Updated spirv-tools.

This commit is contained in:
Бранимир Караџић 2019-03-22 13:17:07 -07:00
parent 027e592038
commit 4c071b3a97
25 changed files with 1885 additions and 374 deletions

View File

@ -1 +1 @@
"v2019.2", "SPIRV-Tools v2019.2 45a64efab9d50ebcf9694a3297acc3223502610d"
"v2019.2", "SPIRV-Tools v2019.2 027e592038bb74c2ffe6b07f9cc4e2ab1b3fbb84"

View File

@ -110,6 +110,16 @@ static const int kInstValidationOutError = kInstStageOutCnt;
// about the validation error.
//
// 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;
// A bindless uninitialized error will output the index.
static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1;
static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2;
static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3;
// DEPRECATED
static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1;
static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2;
static const int kInstBindlessOutCnt = kInstStageOutCnt + 3;
@ -121,6 +131,7 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
//
// These are the possible validation error codes.
static const int kInstErrorBindlessBounds = 0;
static const int kInstErrorBindlessUninit = 1;
// Direct Input Buffer Offsets
//
@ -141,11 +152,8 @@ static const int kDebugInputDataOffset = 0;
// and possibly other future validations.
static const int kDebugOutputBindingStream = 0;
// The binding for the input buffer for InstBindlessCheckPass. The input
// buffer needs only be created if the shaders being validated contain a
// descriptor array of runtime size, and validation of runtime size descriptor
// arrays have been enabled at the time of the bindless validation pass
// creation.
// The binding for the input buffer read by InstBindlessCheckPass and
// possibly other future validations.
static const int kDebugInputBindingBindless = 1;
// Bindless Validation Input Buffer Format
@ -153,11 +161,19 @@ static const int kDebugInputBindingBindless = 1;
// An input buffer for bindless validation consists of a single array of
// unsigned integers we will call Data[]. This array is formatted as follows.
//
// At the beginning of the array is a single uint reserved for future use.
// 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:
// Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ]
static const int kDebugInputBindlessInitOffset = 0;
// DEPRECATED
static const int kDebugInputBindlessOffsetReserved = 0;
// Following the reserved uint is some number of uints such that the following
// is true: the number of descriptors at (set=s, binding=b) is:
// At offset kDebugInputBindlessOffsetLengths is some number of uints which
// provide the bindless length data. More specifically, the number of
// descriptors at (set=s, binding=b) is:
// Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ]
static const int kDebugInputBindlessOffsetLengths = 1;

View File

@ -704,10 +704,14 @@ Optimizer::PassToken CreateCombineAccessChainsPass();
// Create a pass to instrument bindless descriptor checking
// This pass instruments all bindless references to check that descriptor
// array indices are inbounds. If the reference is invalid, a record is
// written to the debug output buffer (if space allows) and a null value is
// returned. This pass is designed to support bindless validation in the Vulkan
// validation layers.
// array indices are inbounds, and if the descriptor indexing extension is
// enabled, that the descriptor has been initialized. If the reference is
// invalid, a record is written to the debug output buffer (if space allows)
// and a null value is returned. This pass is designed to support bindless
// validation in the Vulkan validation layers.
//
// TODO(greg-lunarg): Add support for buffer references. Currently only does
// checking for image references.
//
// Dead code elimination should be run after this pass as the original,
// potentially invalid code is not removed and could cause undefined behavior,
@ -723,12 +727,12 @@ Optimizer::PassToken CreateCombineAccessChainsPass();
// The instrumentation will read and write buffers in debug
// descriptor set |desc_set|. It will write |shader_id| in each output record
// to identify the shader module which generated the record.
// |runtime_array_enable| controls instrumentation of runtime arrays which
// require input buffer support.
//
// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
// |input_length_enable| controls instrumentation of runtime descriptor array
// references, and |input_init_enable| controls instrumentation of descriptor
// initialization checking, both of which require input buffer support.
Optimizer::PassToken CreateInstBindlessCheckPass(
uint32_t desc_set, uint32_t shader_id, bool runtime_array_enable = false);
uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false,
bool input_init_enable = false);
// Create a pass to upgrade to the VulkanKHR memory model.
// This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.

View File

@ -40,21 +40,74 @@ uint32_t InstBindlessCheckPass::GenDebugReadLength(
uint32_t desc_set_idx =
var2desc_set_[var_id] + kDebugInputBindlessOffsetLengths;
uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx);
uint32_t desc_set_offset_id = GenDebugDirectRead(desc_set_idx_id, builder);
Instruction* binding_idx_inst =
builder->AddBinaryOp(GetUintId(), SpvOpIAdd, desc_set_offset_id,
builder->GetUintConstantId(var2binding_[var_id]));
return GenDebugDirectRead(binding_idx_inst->result_id(), builder);
uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
return GenDebugDirectRead({desc_set_idx_id, binding_idx_id}, builder);
}
void InstBindlessCheckPass::GenBindlessCheckCode(
BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
// Look for reference through bindless descriptor. If not, return.
std::unique_ptr<BasicBlock> new_blk_ptr;
uint32_t image_id;
switch (ref_inst_itr->opcode()) {
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);
}
uint32_t InstBindlessCheckPass::CloneOriginalReference(
ref_analysis* ref, InstructionBuilder* builder) {
// Clone descriptor load
Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
Instruction* new_load_inst =
builder->AddLoad(load_inst->type_id(),
load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
uid2offset_[new_load_inst->unique_id()] = uid2offset_[load_inst->unique_id()];
uint32_t new_load_id = new_load_inst->result_id();
get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
uint32_t new_image_id = new_load_id;
// Clone Image/SampledImage with new load, if needed
if (ref->image_id != 0) {
Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
Instruction* new_image_inst = builder->AddBinaryOp(
image_inst->type_id(), SpvOpSampledImage, new_load_id,
image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
uid2offset_[new_image_inst->unique_id()] =
uid2offset_[image_inst->unique_id()];
new_image_id = new_image_inst->result_id();
} else {
assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
Instruction* new_image_inst =
builder->AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
uid2offset_[new_image_inst->unique_id()] =
uid2offset_[image_inst->unique_id()];
new_image_id = new_image_inst->result_id();
}
get_decoration_mgr()->CloneDecorations(ref->image_id, new_image_id);
}
// Clone original reference using new image code
std::unique_ptr<Instruction> new_ref_inst(ref->ref_inst->Clone(context()));
uint32_t ref_result_id = ref->ref_inst->result_id();
uint32_t new_ref_id = 0;
if (ref_result_id != 0) {
new_ref_id = TakeNextId();
new_ref_inst->SetResultId(new_ref_id);
}
new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
// Register new reference and add to new block
Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
uid2offset_[added_inst->unique_id()] =
uid2offset_[ref->ref_inst->unique_id()];
if (new_ref_id != 0)
get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
return new_ref_id;
}
uint32_t InstBindlessCheckPass::GetDescriptorValueId(Instruction* inst) {
switch (inst->opcode()) {
case SpvOp::SpvOpImageSampleImplicitLod:
case SpvOp::SpvOpImageSampleExplicitLod:
case SpvOp::SpvOpImageSampleDrefImplicitLod:
@ -87,162 +140,205 @@ void InstBindlessCheckPass::GenBindlessCheckCode(
case SpvOp::SpvOpImageSparseFetch:
case SpvOp::SpvOpImageSparseRead:
case SpvOp::SpvOpImageWrite:
image_id =
ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
break;
return inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
default:
return;
break;
}
Instruction* image_inst = get_def_use_mgr()->GetDef(image_id);
uint32_t load_id;
Instruction* load_inst;
return 0;
}
bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
ref_analysis* ref) {
ref->image_id = GetDescriptorValueId(ref_inst);
if (ref->image_id == 0) return false;
Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
load_inst = get_def_use_mgr()->GetDef(load_id);
ref->load_id =
image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
} else if (image_inst->opcode() == SpvOp::SpvOpImage) {
load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
load_inst = get_def_use_mgr()->GetDef(load_id);
ref->load_id =
image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
} else {
load_id = image_id;
load_inst = image_inst;
image_id = 0;
ref->load_id = ref->image_id;
ref->image_id = 0;
}
Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
if (load_inst->opcode() != SpvOp::SpvOpLoad) {
// TODO(greg-lunarg): Handle additional possibilities
return;
// TODO(greg-lunarg): Handle additional possibilities?
return false;
}
uint32_t ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
if (ptr_inst->NumInOperands() != 2) {
assert(false && "unexpected bindless index number");
return;
ref->ptr_id = 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->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 =
ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
if (var_inst->opcode() != SpvOpVariable) {
assert(false && "unexpected bindless base");
return false;
}
} else {
// TODO(greg-lunarg): Handle additional possibilities?
return false;
}
uint32_t index_id =
ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
ptr_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
if (ptr_inst->opcode() != SpvOpVariable) {
assert(false && "unexpected bindless base");
return;
}
uint32_t var_type_id = ptr_inst->type_id();
Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
uint32_t ptr_type_id =
var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
// If index and bound both compile-time constants and index < bound,
// return without changing
uint32_t length_id = 0;
if (ptr_type_inst->opcode() == SpvOpTypeArray) {
length_id =
ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
Instruction* index_inst = get_def_use_mgr()->GetDef(index_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 (!runtime_array_enabled_ ||
ptr_type_inst->opcode() != SpvOpTypeRuntimeArray) {
return;
}
// Generate full runtime bounds test code with true branch
// being full reference and false branch being debug output and zero
// for the referenced value.
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
ref->ref_inst = ref_inst;
return true;
}
void InstBindlessCheckPass::GenCheckCode(
uint32_t check_id, uint32_t error_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(
context(), &*new_blk_ptr,
context(), back_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
// If length id not yet set, descriptor array is runtime size so
// generate load of length from stage's debug input buffer.
if (length_id == 0) {
assert(ptr_type_inst->opcode() == SpvOpTypeRuntimeArray &&
"unexpected bindless type");
length_id = GenDebugReadLength(ptr_id, &builder);
}
Instruction* ult_inst =
builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
// Gen conditional branch on check_id. Valid branch generates original
// reference. Invalid generates debug output and zero result (if needed).
uint32_t merge_blk_id = TakeNextId();
uint32_t valid_blk_id = TakeNextId();
uint32_t invalid_blk_id = TakeNextId();
std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
(void)builder.AddConditionalBranch(ult_inst->result_id(), valid_blk_id,
invalid_blk_id, merge_blk_id,
SpvSelectionControlMaskNone);
// Close selection block and gen valid reference block
new_blocks->push_back(std::move(new_blk_ptr));
new_blk_ptr.reset(new BasicBlock(std::move(valid_label)));
(void)builder.AddConditionalBranch(check_id, valid_blk_id, invalid_blk_id,
merge_blk_id, SpvSelectionControlMaskNone);
// Gen valid bounds branch
std::unique_ptr<BasicBlock> new_blk_ptr(
new BasicBlock(std::move(valid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// Clone descriptor load
Instruction* new_load_inst =
builder.AddLoad(load_inst->type_id(),
load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
uint32_t new_load_id = new_load_inst->result_id();
get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
uint32_t new_image_id = new_load_id;
// Clone Image/SampledImage with new load, if needed
if (image_id != 0) {
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
Instruction* new_image_inst = builder.AddBinaryOp(
image_inst->type_id(), SpvOpSampledImage, new_load_id,
image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
new_image_id = new_image_inst->result_id();
} else {
assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
Instruction* new_image_inst =
builder.AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
new_image_id = new_image_inst->result_id();
}
get_decoration_mgr()->CloneDecorations(image_id, new_image_id);
}
// Clone original reference using new image code
std::unique_ptr<Instruction> new_ref_inst(ref_inst_itr->Clone(context()));
uint32_t ref_result_id = ref_inst_itr->result_id();
uint32_t new_ref_id = 0;
if (ref_result_id != 0) {
new_ref_id = TakeNextId();
new_ref_inst->SetResultId(new_ref_id);
}
new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
// Register new reference and add to new block
builder.AddInstruction(std::move(new_ref_inst));
if (new_ref_id != 0)
get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
// Close valid block and gen invalid block
uint32_t new_ref_id = CloneOriginalReference(ref, &builder);
(void)builder.AddBranch(merge_blk_id);
new_blocks->push_back(std::move(new_blk_ptr));
// Gen invalid block
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
uint32_t u_index_id = GenUintCastCode(index_id, &builder);
GenDebugStreamWrite(instruction_idx, stage_idx,
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);
// Remember last invalid block id
uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
// Gen zero for invalid reference
uint32_t ref_type_id = ref_inst_itr->type_id();
// Close invalid block and gen merge block
uint32_t ref_type_id = ref->ref_inst->type_id();
(void)builder.AddBranch(merge_blk_id);
new_blocks->push_back(std::move(new_blk_ptr));
// Gen merge block
new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// Gen phi of new reference and zero, if necessary, and replace the
// result id of the original reference with that of the Phi. Kill original
// reference and move in remainder of original block.
// reference.
if (new_ref_id != 0) {
Instruction* phi_inst = builder.AddPhi(
ref_type_id, {new_ref_id, valid_blk_id, builder.GetNullId(ref_type_id),
last_invalid_blk_id});
context()->ReplaceAllUsesWith(ref_result_id, phi_inst->result_id());
context()->ReplaceAllUsesWith(ref->ref_inst->result_id(),
phi_inst->result_id());
}
context()->KillInst(&*ref_inst_itr);
MovePostludeCode(ref_block_itr, &new_blk_ptr);
// Add remainder/merge block to new blocks
new_blocks->push_back(std::move(new_blk_ptr));
context()->KillInst(ref->ref_inst);
}
void InstBindlessCheckPass::GenBoundsCheckCode(
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 indexed descriptor. If found, analyze and
// save components. If not, return.
ref_analysis ref;
if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
// 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);
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);
Instruction* desc_type_inst = get_def_use_mgr()->GetDef(desc_type_id);
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* 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_ ||
desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
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);
InstructionBuilder builder(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
new_blocks->push_back(std::move(new_blk_ptr));
uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
// If length id not yet set, descriptor array is runtime size so
// generate load of length from stage's debug input buffer.
if (length_id == 0) {
assert(desc_type_inst->opcode() == SpvOpTypeRuntimeArray &&
"unexpected bindless type");
length_id = GenDebugReadLength(ref.var_id, &builder);
}
// 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,
new_blocks);
// Move original block's remaining code into remainder/merge block and add
// to new blocks
BasicBlock* back_blk_ptr = &*new_blocks->back();
MovePostludeCode(ref_block_itr, back_blk_ptr);
}
void InstBindlessCheckPass::GenInitCheckCode(
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;
// 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);
InstructionBuilder builder(
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
// 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
// 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);
// Move original block's remaining code into remainder/merge block and add
// to new blocks
BasicBlock* back_blk_ptr = &*new_blocks->back();
MovePostludeCode(ref_block_itr, back_blk_ptr);
}
void InstBindlessCheckPass::InitializeInstBindlessCheck() {
@ -258,9 +354,10 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() {
break;
}
}
// If descriptor indexing extension and runtime array support enabled,
// create variable to descriptor set mapping.
if (ext_descriptor_indexing_defined_ && runtime_array_enabled_)
// If descriptor indexing extension and runtime array length support enabled,
// create variable mappings. Length support is always enabled if descriptor
// init check is enabled.
if (ext_descriptor_indexing_defined_ && input_length_enabled_)
for (auto& anno : get_module()->annotations())
if (anno.opcode() == SpvOpDecorate) {
if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
@ -273,18 +370,27 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() {
}
Pass::Status InstBindlessCheckPass::ProcessImpl() {
// Perform instrumentation on each entry point function in module
// Perform bindless bounds check on each entry point function in module
InstProcessFunction pfn =
[this](BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr,
uint32_t instruction_idx, uint32_t stage_idx,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
return GenBindlessCheckCode(ref_inst_itr, ref_block_itr,
instruction_idx, stage_idx, new_blocks);
return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
new_blocks);
};
bool modified = InstProcessEntryPointCallTree(pfn);
// This pass does not update inst->blk info
context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping);
if (ext_descriptor_indexing_defined_ && input_init_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);
};
modified |= InstProcessEntryPointCallTree(pfn);
}
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@ -31,12 +31,14 @@ class InstBindlessCheckPass : public InstrumentPass {
// For test harness only
InstBindlessCheckPass()
: InstrumentPass(7, 23, kInstValidationIdBindless),
runtime_array_enabled_(true) {}
input_length_enabled_(true),
input_init_enabled_(true) {}
// For all other interfaces
InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
bool runtime_array_enable)
bool input_length_enable, bool input_init_enable)
: InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
runtime_array_enabled_(runtime_array_enable) {}
input_length_enabled_(input_length_enable),
input_init_enabled_(input_init_enable) {}
~InstBindlessCheckPass() override = default;
@ -46,32 +48,40 @@ class InstBindlessCheckPass : public InstrumentPass {
const char* name() const override { return "inst-bindless-check-pass"; }
private:
// Generate instructions into |builder| to read length of runtime descriptor
// array |var_id| from debug input buffer and return id of value.
uint32_t GenDebugReadLength(uint32_t var_id, InstructionBuilder* builder);
// Initialize state for instrumenting bindless checking
void InitializeInstBindlessCheck();
// This function does bindless checking instrumentation on a single
// instruction. It is designed to be passed to
// 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
// 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.
//
// The functions are designed to be passed to
// InstrumentPass::InstProcessEntryPointCallTree(), which applies the
// function to each instruction in a module and replaces the instruction
// if warranted.
//
// If |ref_inst_itr| is a bindless reference, return in |new_blocks| the
// result of instrumenting it with validation code within its block at
// |ref_block_itr|. Specifically, generate code to check that the index
// into the descriptor array is in-bounds. If the check passes, execute
// the remainder of the reference, otherwise write a record to the debug
// |ref_block_itr|. The validation code first executes a check for the
// specific condition called for. If the check passes, it executes
// the remainder of the reference, otherwise writes a record to the debug
// output buffer stream including |function_idx, instruction_idx, stage_idx|
// and replace the reference with the null value of the original type. The
// and replaces the reference with the null value of the original type. The
// block at |ref_block_itr| can just be replaced with the blocks in
// |new_blocks|, which will contain at least two blocks. The last block will
// comprise all instructions following |ref_inst_itr|,
// preceded by a phi instruction.
//
// This instrumentation pass utilizes GenDebugStreamWrite() to write its
// These instrumentation functions utilize GenDebugDirectRead() to read data
// from the debug input buffer, specifically the lengths of variable length
// descriptor arrays, and the initialization status of each descriptor.
// The format of the debug input buffer is documented in instrument.hpp.
//
// These instrumentation functions utilize GenDebugStreamWrite() to write its
// error records. The validation-specific part of the error record will
// have the format:
//
@ -84,18 +94,77 @@ class InstBindlessCheckPass : public InstrumentPass {
//
// The Descriptor Array Size is the size of the descriptor array which was
// indexed.
void GenBindlessCheckCode(
BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
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 GenInitCheckCode(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.
uint32_t GenDebugReadLength(uint32_t var_id, InstructionBuilder* builder);
// Generate instructions into |builder| to read initialization status of
// descriptor array |image_id| at |index_id| from debug input buffer and
// return id of value.
uint32_t GenDebugReadInit(uint32_t image_id, uint32_t index_id,
InstructionBuilder* builder);
// Analysis data for descriptor reference components, generated by
// AnalyzeDescriptorReference. It is necessary and sufficient for further
// analysis and regeneration of the reference.
typedef struct ref_analysis {
uint32_t image_id;
uint32_t load_id;
uint32_t ptr_id;
uint32_t var_id;
uint32_t index_id;
Instruction* ref_inst;
} ref_analysis;
// Clone original original reference encapsulated by |ref| into |builder|.
// This may generate more than one instruction if neccessary.
uint32_t CloneOriginalReference(ref_analysis* ref,
InstructionBuilder* builder);
// If |inst| references through a descriptor, (ie references into an image
// or buffer), return the id of the value it references. Else return 0.
uint32_t GetDescriptorValueId(Instruction* inst);
// Analyze descriptor reference |ref_inst| and save components into |ref|.
// Return true if |ref_inst| is a descriptor reference, false otherwise.
bool AnalyzeDescriptorReference(Instruction* ref_inst, ref_analysis* ref);
// Generate instrumentation code for generic test result |check_id|, starting
// with |builder| of block |new_blk_ptr|, adding new blocks to |new_blocks|.
// Generate conditional branch to a valid or invalid branch. Generate valid
// block which does original reference |ref|. Generate invalid block which
// 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,
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.
Pass::Status ProcessImpl();
// True if VK_EXT_descriptor_indexing is defined
bool ext_descriptor_indexing_defined_;
// Enable instrumentation of runtime arrays
bool runtime_array_enabled_;
// Enable instrumentation of runtime array length checking
bool input_length_enabled_;
// Enable instrumentation of descriptor initialization checking
bool input_init_enabled_;
// Mapping from variable to descriptor set
std::unordered_map<uint32_t, uint32_t> var2desc_set_;

View File

@ -57,8 +57,7 @@ void InstrumentPass::MovePreludeCode(
}
void InstrumentPass::MovePostludeCode(
UptrVectorIterator<BasicBlock> ref_block_itr,
std::unique_ptr<BasicBlock>* new_blk_ptr) {
UptrVectorIterator<BasicBlock> ref_block_itr, BasicBlock* new_blk_ptr) {
// new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id())));
// Move contents of original ref block.
for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
@ -77,7 +76,7 @@ void InstrumentPass::MovePostludeCode(
same_block_post_[rid] = rid;
}
}
(*new_blk_ptr)->AddInstruction(std::move(mv_inst));
new_blk_ptr->AddInstruction(std::move(mv_inst));
}
}
@ -222,16 +221,14 @@ void InstrumentPass::GenDebugStreamWrite(
(void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
}
uint32_t InstrumentPass::GenDebugDirectRead(uint32_t idx_id,
InstructionBuilder* builder) {
uint32_t input_buf_id = GetInputBufferId();
uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
Instruction* ibuf_ac_inst = builder->AddTernaryOp(
buf_uint_ptr_id, SpvOpAccessChain, input_buf_id,
builder->GetUintConstantId(kDebugInputDataOffset), idx_id);
Instruction* load_inst =
builder->AddUnaryOp(GetUintId(), SpvOpLoad, ibuf_ac_inst->result_id());
return load_inst->result_id();
uint32_t InstrumentPass::GenDebugDirectRead(
const std::vector<uint32_t>& offset_ids, InstructionBuilder* 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();
}
bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
@ -242,7 +239,7 @@ void InstrumentPass::CloneSameBlockOps(
std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* same_blk_post,
std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
std::unique_ptr<BasicBlock>* block_ptr) {
BasicBlock* block_ptr) {
(*inst)->ForEachInId(
[&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) {
const auto map_itr = (*same_blk_post).find(*iid);
@ -259,7 +256,7 @@ void InstrumentPass::CloneSameBlockOps(
sb_inst->SetResultId(nid);
(*same_blk_post)[rid] = nid;
*iid = nid;
(*block_ptr)->AddInstruction(std::move(sb_inst));
block_ptr->AddInstruction(std::move(sb_inst));
}
} else {
// Reset same-block op operand.
@ -604,6 +601,81 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx,
return output_func_id_;
}
uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
uint32_t func_id = param2input_func_id_[param_cnt];
if (func_id != 0) return func_id;
// Create input function for param_cnt
func_id = TakeNextId();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
std::vector<const analysis::Type*> param_types;
for (uint32_t c = 0; c < param_cnt; ++c)
param_types.push_back(type_mgr->GetType(GetUintId()));
analysis::Function func_ty(type_mgr->GetType(GetUintId()), param_types);
analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
std::unique_ptr<Instruction> func_inst(new Instruction(
get_module()->context(), SpvOpFunction, GetUintId(), func_id,
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{SpvFunctionControlMaskNone}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{type_mgr->GetTypeInstruction(reg_func_ty)}}}));
get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
std::unique_ptr<Function> input_func =
MakeUnique<Function>(std::move(func_inst));
// Add parameters
std::vector<uint32_t> param_vec;
for (uint32_t c = 0; c < param_cnt; ++c) {
uint32_t pid = TakeNextId();
param_vec.push_back(pid);
std::unique_ptr<Instruction> param_inst(new Instruction(
get_module()->context(), SpvOpFunctionParameter, GetUintId(), pid, {}));
get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
input_func->AddParameter(std::move(param_inst));
}
// Create block
uint32_t blk_id = TakeNextId();
std::unique_ptr<Instruction> blk_label(NewLabel(blk_id));
std::unique_ptr<BasicBlock> new_blk_ptr =
MakeUnique<BasicBlock>(std::move(blk_label));
InstructionBuilder builder(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
// For each offset parameter, generate new offset with parameter, adding last
// loaded value if it exists, and load value from input buffer at new offset.
// Return last loaded value.
uint32_t buf_id = GetInputBufferId();
uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
uint32_t last_value_id = 0;
for (uint32_t p = 0; p < param_cnt; ++p) {
uint32_t offset_id;
if (p == 0) {
offset_id = param_vec[0];
} else {
Instruction* offset_inst = builder.AddBinaryOp(
GetUintId(), SpvOpIAdd, last_value_id, param_vec[p]);
offset_id = offset_inst->result_id();
}
Instruction* ac_inst = builder.AddTernaryOp(
buf_uint_ptr_id, SpvOpAccessChain, buf_id,
builder.GetUintConstantId(kDebugInputDataOffset), offset_id);
Instruction* load_inst =
builder.AddUnaryOp(GetUintId(), SpvOpLoad, ac_inst->result_id());
last_value_id = load_inst->result_id();
}
(void)builder.AddInstruction(MakeUnique<Instruction>(
context(), SpvOpReturnValue, 0, 0,
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {last_value_id}}}));
// Close block and function and add function to module
new_blk_ptr->SetParent(&*input_func);
input_func->AddBasicBlock(std::move(new_blk_ptr));
std::unique_ptr<Instruction> func_end_inst(
new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {}));
get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
input_func->SetFunctionEnd(std::move(func_end_inst));
context()->AddFunction(std::move(input_func));
param2input_func_id_[param_cnt] = func_id;
return func_id;
}
bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
InstProcessFunction& pfn) {
bool modified = false;
@ -614,23 +686,17 @@ bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
++function_idx;
}
std::vector<std::unique_ptr<BasicBlock>> new_blks;
// Start count after function and param instructions
uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
func->ForEachParam(
[&instruction_idx](const Instruction*) { ++instruction_idx; }, true);
// Using block iterators here because of block erasures and insertions.
for (auto bi = func->begin(); bi != func->end(); ++bi) {
// Count block's label
++instruction_idx;
for (auto ii = bi->begin(); ii != bi->end(); ++instruction_idx) {
// Bump instruction count if debug instructions
instruction_idx += static_cast<uint32_t>(ii->dbg_line_insts().size());
for (auto ii = bi->begin(); ii != bi->end();) {
// Generate instrumentation if warranted
pfn(ii, bi, instruction_idx, stage_idx, &new_blks);
pfn(ii, bi, stage_idx, &new_blks);
if (new_blks.size() == 0) {
++ii;
continue;
}
// Add new blocks to label id map
for (auto& blk : new_blks) id2block_[blk->id()] = &*blk;
// If there are new blocks we know there will always be two or
// more, so update succeeding phis with label of new last block.
size_t newBlocksSize = new_blks.size();
@ -660,6 +726,9 @@ bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
uint32_t stage_idx) {
bool modified = false;
std::unordered_set<uint32_t> done;
// Don't process input and output functions
for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
if (output_func_id_ != 0) done.insert(output_func_id_);
// Process all functions from roots
while (!roots->empty()) {
const uint32_t fi = roots->front();
@ -735,72 +804,68 @@ void InstrumentPass::InitializeInstrument() {
}
}
// Calculate instruction offset of first function
uint32_t pre_func_size = 0;
// Remember original instruction offsets
uint32_t module_offset = 0;
Module* module = get_module();
for (auto& i : context()->capabilities()) {
(void)i;
++pre_func_size;
++module_offset;
}
for (auto& i : module->extensions()) {
(void)i;
++pre_func_size;
++module_offset;
}
for (auto& i : module->ext_inst_imports()) {
(void)i;
++pre_func_size;
++module_offset;
}
++pre_func_size; // memory_model
++module_offset; // memory_model
for (auto& i : module->entry_points()) {
(void)i;
++pre_func_size;
++module_offset;
}
for (auto& i : module->execution_modes()) {
(void)i;
++pre_func_size;
++module_offset;
}
for (auto& i : module->debugs1()) {
(void)i;
++pre_func_size;
++module_offset;
}
for (auto& i : module->debugs2()) {
(void)i;
++pre_func_size;
++module_offset;
}
for (auto& i : module->debugs3()) {
(void)i;
++pre_func_size;
++module_offset;
}
for (auto& i : module->annotations()) {
(void)i;
++pre_func_size;
++module_offset;
}
for (auto& i : module->types_values()) {
pre_func_size += 1;
pre_func_size += static_cast<uint32_t>(i.dbg_line_insts().size());
module_offset += 1;
module_offset += static_cast<uint32_t>(i.dbg_line_insts().size());
}
funcIdx2offset_[0] = pre_func_size;
// Set instruction offsets for all other functions.
uint32_t func_idx = 1;
auto prev_fn = get_module()->begin();
auto curr_fn = prev_fn;
for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
// Count function, end and param instructions
uint32_t func_size = 2;
prev_fn->ForEachParam([&func_size](const Instruction*) { ++func_size; },
true);
for (auto& blk : *prev_fn) {
auto curr_fn = get_module()->begin();
for (; curr_fn != get_module()->end(); ++curr_fn) {
// Count function instruction
module_offset += 1;
curr_fn->ForEachParam(
[&module_offset](const Instruction*) { module_offset += 1; }, true);
for (auto& blk : *curr_fn) {
// Count label
func_size += 1;
module_offset += 1;
for (auto& inst : blk) {
func_size += 1;
func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
module_offset += static_cast<uint32_t>(inst.dbg_line_insts().size());
uid2offset_[inst.unique_id()] = module_offset;
module_offset += 1;
}
}
funcIdx2offset_[func_idx] = funcIdx2offset_[func_idx - 1] + func_size;
++prev_fn;
++func_idx;
// Count function end instruction
module_offset += 1;
}
}

View File

@ -65,18 +65,16 @@ class InstrumentPass : public Pass {
using cbb_ptr = const BasicBlock*;
public:
using InstProcessFunction = std::function<void(
BasicBlock::iterator, UptrVectorIterator<BasicBlock>, uint32_t, uint32_t,
std::vector<std::unique_ptr<BasicBlock>>*)>;
using InstProcessFunction =
std::function<void(BasicBlock::iterator, UptrVectorIterator<BasicBlock>,
uint32_t, std::vector<std::unique_ptr<BasicBlock>>*)>;
~InstrumentPass() override = default;
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisDefUse |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId |
IRContext::kAnalysisConstants;
return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap |
IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants;
}
protected:
@ -107,7 +105,7 @@ class InstrumentPass : public Pass {
// Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr|
// to be instrumented into block |new_blk_ptr|.
void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
std::unique_ptr<BasicBlock>* new_blk_ptr);
BasicBlock* new_blk_ptr);
// Generate instructions in |builder| which will atomically fetch and
// increment the size of the debug output buffer stream of the current
@ -196,11 +194,15 @@ class InstrumentPass : public Pass {
InstructionBuilder* builder);
// Generate in |builder| instructions to read the unsigned integer from the
// input buffer at offset |idx_id|. Return the result id.
// input buffer specified by the offsets in |offset_ids|. Given offsets
// o0, o1, ... oN, and input buffer ibuf, return the id for the value:
//
// ibuf[...ibuf[ibuf[o0]+o1]...+oN]
//
// The binding and the format of the input buffer is determined by each
// specific validation, which is specified at the creation of the pass.
uint32_t GenDebugDirectRead(uint32_t idx_id, InstructionBuilder* builder);
uint32_t GenDebugDirectRead(const std::vector<uint32_t>& offset_ids,
InstructionBuilder* builder);
// Generate code to cast |value_id| to unsigned, if needed. Return
// an id to the unsigned equivalent.
@ -247,10 +249,14 @@ class InstrumentPass : public Pass {
uint32_t GetVec4UintId();
// Return id for output function. Define if it doesn't exist with
// |val_spec_arg_cnt| validation-specific uint32 arguments.
// |val_spec_param_cnt| validation-specific uint32 parameters.
uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
uint32_t val_spec_param_cnt);
// Return id for input function taking |param_cnt| uint32 parameters. Define
// if it doesn't exist.
uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
// 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
@ -312,7 +318,7 @@ class InstrumentPass : public Pass {
std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* same_blk_post,
std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
std::unique_ptr<BasicBlock>* block_ptr);
BasicBlock* block_ptr);
// Update phis in succeeding blocks to point to new last block
void UpdateSucceedingPhis(
@ -331,8 +337,8 @@ class InstrumentPass : public Pass {
// CFG. It has functionality not present in CFG. Consolidate.
std::unordered_map<uint32_t, BasicBlock*> id2block_;
// Map from function's position index to the offset of its first instruction
std::unordered_map<uint32_t, uint32_t> funcIdx2offset_;
// Map from instruction's unique id to offset in original file.
std::unordered_map<uint32_t, uint32_t> uid2offset_;
// result id for OpConstantFalse
uint32_t validation_id_;
@ -346,6 +352,9 @@ class InstrumentPass : public Pass {
// id for debug output function
uint32_t output_func_id_;
// ids for debug input functions
std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
// param count for output function
uint32_t output_func_param_cnt_;

View File

@ -387,7 +387,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
} else if (pass_name == "replace-invalid-opcode") {
RegisterPass(CreateReplaceInvalidOpcodePass());
} else if (pass_name == "inst-bindless-check") {
RegisterPass(CreateInstBindlessCheckPass(7, 23, true));
RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true));
RegisterPass(CreateSimplificationPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateBlockMergePass());
@ -807,10 +807,11 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
uint32_t shader_id,
bool runtime_array_enable) {
bool input_length_enable,
bool input_init_enable) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id,
runtime_array_enable));
MakeUnique<opt::InstBindlessCheckPass>(
desc_set, shader_id, input_length_enable, input_init_enable));
}
Optimizer::PassToken CreateCodeSinkingPass() {

View File

@ -24,6 +24,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
reduction_opportunity_finder.h
reduction_pass.h
reduction_util.h
remove_block_reduction_opportunity.h
remove_block_reduction_opportunity_finder.h
remove_instruction_reduction_opportunity.h
remove_function_reduction_opportunity.h
remove_function_reduction_opportunity_finder.h
@ -43,6 +45,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
reduction_opportunity.cpp
reduction_pass.cpp
reduction_util.cpp
remove_block_reduction_opportunity.cpp
remove_block_reduction_opportunity_finder.cpp
remove_function_reduction_opportunity.cpp
remove_function_reduction_opportunity_finder.cpp
remove_instruction_reduction_opportunity.cpp

View File

@ -53,13 +53,25 @@ void Reducer::SetInterestingnessFunction(
Reducer::ReductionResultStatus Reducer::Run(
std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
spv_const_reducer_options options) const {
std::vector<uint32_t> current_binary = binary_in;
spv_const_reducer_options options,
spv_validator_options validator_options) const {
std::vector<uint32_t> current_binary(std::move(binary_in));
spvtools::SpirvTools tools(impl_->target_env);
assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
// Keeps track of how many reduction attempts have been tried. Reduction
// bails out if this reaches a given limit.
uint32_t reductions_applied = 0;
// Initial state should be valid.
if (!tools.Validate(&current_binary[0], current_binary.size(),
validator_options)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Initial binary is invalid; stopping.");
return Reducer::ReductionResultStatus::kInitialStateInvalid;
}
// Initial state should be interesting.
if (!impl_->interestingness_function(current_binary, reductions_applied)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
@ -106,7 +118,8 @@ Reducer::ReductionResultStatus Reducer::Run(
<< reductions_applied << ".";
impl_->consumer(SPV_MSG_INFO, nullptr, {},
(stringstream.str().c_str()));
if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) {
if (!tools.Validate(&maybe_result[0], maybe_result.size(),
validator_options)) {
// The reduction step went wrong and an invalid binary was produced.
// By design, this shouldn't happen; this is a safeguard to stop an
// invalid binary from being regarded as interesting.

View File

@ -33,7 +33,8 @@ class Reducer {
enum ReductionResultStatus {
kInitialStateNotInteresting,
kReachedStepLimit,
kComplete
kComplete,
kInitialStateInvalid
};
// The type for a function that will take a binary and return true if and
@ -84,7 +85,8 @@ class Reducer {
// A status is returned.
ReductionResultStatus Run(std::vector<uint32_t>&& binary_in,
std::vector<uint32_t>* binary_out,
spv_const_reducer_options options) const;
spv_const_reducer_options options,
spv_validator_options validator_options) const;
private:
struct Impl; // Opaque struct for holding internal data.

View File

@ -0,0 +1,56 @@
// Copyright (c) 2019 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 "remove_block_reduction_opportunity.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace reduce {
using namespace opt;
RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
Function* function, BasicBlock* block)
: function_(function), block_(block) {
// precondition:
assert(block_->begin() != block_->end() &&
block_->begin()->context()->get_def_use_mgr()->NumUsers(
block_->id()) == 0 &&
"RemoveBlockReductionOpportunity block must have 0 references");
}
bool RemoveBlockReductionOpportunity::PreconditionHolds() {
// Removing other blocks cannot disable this opportunity.
return true;
}
void RemoveBlockReductionOpportunity::Apply() {
// We need an iterator pointing to the block, hence the loop.
for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
if (bi->id() == block_->id()) {
bi->KillAllInsts(true);
bi.Erase();
// Block removal changes the function, but we don't use analyses, so no
// need to invalidate them.
return;
}
}
assert(false &&
"Unreachable: we should have found a block with the desired id.");
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,46 @@
// Copyright (c) 2019 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.
#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
#include "reduction_opportunity.h"
#include "source/opt/basic_block.h"
#include "source/opt/function.h"
namespace spvtools {
namespace reduce {
// An opportunity to remove an unreferenced block.
// See RemoveBlockReductionOpportunityFinder.
class RemoveBlockReductionOpportunity : public ReductionOpportunity {
public:
// Creates the opportunity to remove |block| in |function| in |context|.
RemoveBlockReductionOpportunity(opt::Function* function,
opt::BasicBlock* block);
bool PreconditionHolds() override;
protected:
void Apply() override;
private:
opt::Function* function_;
opt::BasicBlock* block_;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_

View File

@ -0,0 +1,96 @@
// Copyright (c) 2019 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 "source/reduce/remove_block_reduction_opportunity_finder.h"
#include "source/reduce/remove_block_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
using namespace opt;
std::string RemoveBlockReductionOpportunityFinder::GetName() const {
return "RemoveBlockReductionOpportunityFinder";
}
std::vector<std::unique_ptr<ReductionOpportunity>>
RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
opt::IRContext* context) 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));
}
}
}
return result;
}
bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
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()) {
return false;
}
// Don't remove blocks with references.
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)) {
return false;
}
return true;
}
bool RemoveBlockReductionOpportunityFinder::
BlockInstructionsHaveNoOutsideReferences(opt::IRContext* context,
const Function::iterator& bi) {
// Get all instructions in block.
std::unordered_set<uint32_t> instructions_in_block;
for (const Instruction& instruction : *bi) {
instructions_in_block.insert(instruction.unique_id());
}
// For each instruction...
for (const 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 {
// 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()) !=
instructions_in_block.end();
});
if (!no_uses_outside_block) {
return false;
}
}
return true;
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,55 @@
// Copyright (c) 2019 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.
#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
#include "source/opt/function.h"
#include "source/reduce/reduction_opportunity_finder.h"
namespace spvtools {
namespace reduce {
// A finder of opportunities to remove a block. The optimizer can remove dead
// code. However, the reducer needs to be able to remove at a fine-grained
// level.
class RemoveBlockReductionOpportunityFinder
: public ReductionOpportunityFinder {
public:
RemoveBlockReductionOpportunityFinder() = default;
~RemoveBlockReductionOpportunityFinder() override = default;
std::string GetName() const final;
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) 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);
// Returns true if the instructions (definitions) in block |bi| have no
// references, except for references from inside the block itself.
static bool BlockInstructionsHaveNoOutsideReferences(
opt::IRContext* context, const opt::Function::iterator& bi);
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_

View File

@ -800,10 +800,9 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
// to OpPhi instructions or OpSelect instructions, or any instructions other
// than the image lookup and query instructions specified to take an operand
// whose type is OpTypeSampledImage.
std::vector<uint32_t> consumers = _.getSampledImageConsumers(inst->id());
std::vector<Instruction*> consumers = _.getSampledImageConsumers(inst->id());
if (!consumers.empty()) {
for (auto consumer_id : consumers) {
const auto consumer_instr = _.FindDef(consumer_id);
for (auto consumer_instr : consumers) {
const auto consumer_opcode = consumer_instr->opcode();
if (consumer_instr->block() != inst->block()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
@ -814,7 +813,7 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
<< _.getIdName(inst->id())
<< "' has a consumer in a different basic "
"block. The consumer instruction <id> is '"
<< _.getIdName(consumer_id) << "'.";
<< _.getIdName(consumer_instr->id()) << "'.";
}
// TODO: The following check is incomplete. We should also check that the
// Sampled Image is not used by instructions that should not take
@ -828,8 +827,8 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
"operands of Op"
<< spvOpcodeString(static_cast<SpvOp>(consumer_opcode)) << "."
<< " Found result <id> '" << _.getIdName(inst->id())
<< "' as an operand of <id> '" << _.getIdName(consumer_id)
<< "'.";
<< "' as an operand of <id> '"
<< _.getIdName(consumer_instr->id()) << "'.";
}
}
}

View File

@ -540,15 +540,15 @@ void ValidationState_t::RegisterInstruction(Instruction* inst) {
const uint32_t operand_word = inst->word(operand.offset);
Instruction* operand_inst = FindDef(operand_word);
if (operand_inst && SpvOpSampledImage == operand_inst->opcode()) {
RegisterSampledImageConsumer(operand_word, inst->id());
RegisterSampledImageConsumer(operand_word, inst);
}
}
}
}
std::vector<uint32_t> ValidationState_t::getSampledImageConsumers(
std::vector<Instruction*> ValidationState_t::getSampledImageConsumers(
uint32_t sampled_image_id) const {
std::vector<uint32_t> result;
std::vector<Instruction*> result;
auto iter = sampled_image_consumers_.find(sampled_image_id);
if (iter != sampled_image_consumers_.end()) {
result = iter->second;
@ -557,8 +557,8 @@ std::vector<uint32_t> ValidationState_t::getSampledImageConsumers(
}
void ValidationState_t::RegisterSampledImageConsumer(uint32_t sampled_image_id,
uint32_t consumer_id) {
sampled_image_consumers_[sampled_image_id].push_back(consumer_id);
Instruction* consumer) {
sampled_image_consumers_[sampled_image_id].push_back(consumer);
}
uint32_t ValidationState_t::getIdBound() const { return id_bound_; }

View File

@ -432,13 +432,13 @@ class ValidationState_t {
return all_definitions_;
}
/// Returns a vector containing the Ids of instructions that consume the given
/// Returns a vector containing the instructions that consume the given
/// SampledImage id.
std::vector<uint32_t> getSampledImageConsumers(uint32_t id) const;
std::vector<Instruction*> getSampledImageConsumers(uint32_t id) const;
/// Records cons_id as a consumer of sampled_image_id.
void RegisterSampledImageConsumer(uint32_t sampled_image_id,
uint32_t cons_id);
Instruction* consumer);
/// Returns the set of Global Variables.
std::unordered_set<uint32_t>& global_vars() { return global_vars_; }
@ -684,7 +684,8 @@ class ValidationState_t {
/// Stores a vector of instructions that use the result of a given
/// OpSampledImage instruction.
std::unordered_map<uint32_t, std::vector<uint32_t>> sampled_image_consumers_;
std::unordered_map<uint32_t, std::vector<Instruction*>>
sampled_image_consumers_;
/// A map of operand IDs and their names defined by the OpName instruction
std::unordered_map<uint32_t, std::string> operand_names_;

View File

@ -1926,7 +1926,7 @@ OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 1
OpDecorate %g_tColor Binding 1
OpDecorate %g_tColor Binding 2
OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
OpDecorate %PerViewConstantBuffer_t Block
OpDecorate %g_sAniso DescriptorSet 1
@ -1980,7 +1980,7 @@ OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 1
OpDecorate %g_tColor Binding 1
OpDecorate %g_tColor Binding 2
OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
OpDecorate %PerViewConstantBuffer_t Block
OpDecorate %g_sAniso DescriptorSet 1
@ -1988,15 +1988,15 @@ OpDecorate %g_sAniso Binding 0
OpDecorate %i_vTextureCoords Location 0
OpDecorate %_entryPointOutput_vColor Location 0
OpDecorate %_runtimearr_uint ArrayStride 4
OpDecorate %_struct_41 Block
OpMemberDecorate %_struct_41 0 Offset 0
OpDecorate %43 DescriptorSet 7
OpDecorate %43 Binding 1
OpDecorate %_struct_65 Block
OpMemberDecorate %_struct_65 0 Offset 0
OpMemberDecorate %_struct_65 1 Offset 4
OpDecorate %67 DescriptorSet 7
OpDecorate %67 Binding 0
OpDecorate %_struct_46 Block
OpMemberDecorate %_struct_46 0 Offset 0
OpDecorate %48 DescriptorSet 7
OpDecorate %48 Binding 1
OpDecorate %_struct_71 Block
OpMemberDecorate %_struct_71 0 Offset 0
OpMemberDecorate %_struct_71 1 Offset 4
OpDecorate %73 DescriptorSet 7
OpDecorate %73 Binding 0
OpDecorate %gl_FragCoord BuiltIn FragCoord
%void = OpTypeVoid
%10 = OpTypeFunction %void
@ -2026,16 +2026,17 @@ OpDecorate %gl_FragCoord BuiltIn FragCoord
%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
%uint_0 = OpConstant %uint 0
%uint_2 = OpConstant %uint 2
%41 = OpTypeFunction %uint %uint %uint
%_runtimearr_uint = OpTypeRuntimeArray %uint
%_struct_41 = OpTypeStruct %_runtimearr_uint
%_ptr_StorageBuffer__struct_41 = OpTypePointer StorageBuffer %_struct_41
%43 = OpVariable %_ptr_StorageBuffer__struct_41 StorageBuffer
%_struct_46 = OpTypeStruct %_runtimearr_uint
%_ptr_StorageBuffer__struct_46 = OpTypePointer StorageBuffer %_struct_46
%48 = OpVariable %_ptr_StorageBuffer__struct_46 StorageBuffer
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%bool = OpTypeBool
%59 = OpTypeFunction %void %uint %uint %uint %uint
%_struct_65 = OpTypeStruct %uint %_runtimearr_uint
%_ptr_StorageBuffer__struct_65 = OpTypePointer StorageBuffer %_struct_65
%67 = OpVariable %_ptr_StorageBuffer__struct_65 StorageBuffer
%65 = OpTypeFunction %void %uint %uint %uint %uint
%_struct_71 = OpTypeStruct %uint %_runtimearr_uint
%_ptr_StorageBuffer__struct_71 = OpTypePointer StorageBuffer %_struct_71
%73 = OpVariable %_ptr_StorageBuffer__struct_71 StorageBuffer
%uint_9 = OpConstant %uint 9
%uint_4 = OpConstant %uint 4
%uint_23 = OpConstant %uint 23
@ -2048,7 +2049,8 @@ OpDecorate %gl_FragCoord BuiltIn FragCoord
%uint_7 = OpConstant %uint 7
%uint_8 = OpConstant %uint 8
%uint_59 = OpConstant %uint 59
%110 = OpConstantNull %v4float
%116 = OpConstantNull %v4float
%119 = OpTypeFunction %uint %uint %uint %uint %uint
)";
const std::string func_before =
@ -2077,84 +2079,436 @@ OpFunctionEnd
%34 = OpLoad %16 %33
%35 = OpLoad %24 %g_sAniso
%36 = OpSampledImage %26 %34 %35
%45 = OpAccessChain %_ptr_StorageBuffer_uint %43 %uint_0 %uint_2
%46 = OpLoad %uint %45
%47 = OpIAdd %uint %46 %uint_1
%48 = OpAccessChain %_ptr_StorageBuffer_uint %43 %uint_0 %47
%49 = OpLoad %uint %48
%51 = OpULessThan %bool %32 %49
OpSelectionMerge %52 None
OpBranchConditional %51 %53 %54
%53 = OpLabel
%55 = OpLoad %16 %33
%56 = OpSampledImage %26 %55 %35
%57 = OpImageSampleImplicitLod %v4float %56 %30
OpBranch %52
%54 = OpLabel
%109 = OpFunctionCall %void %58 %uint_59 %uint_0 %32 %49
OpBranch %52
%52 = OpLabel
%111 = OpPhi %v4float %57 %53 %110 %54
OpStore %_entryPointOutput_vColor %111
%55 = OpFunctionCall %uint %40 %uint_2 %uint_2
%57 = OpULessThan %bool %32 %55
OpSelectionMerge %58 None
OpBranchConditional %57 %59 %60
%59 = OpLabel
%61 = OpLoad %16 %33
%62 = OpSampledImage %26 %61 %35
%136 = OpFunctionCall %uint %118 %uint_0 %uint_1 %uint_2 %32
%137 = OpINotEqual %bool %136 %uint_0
OpSelectionMerge %138 None
OpBranchConditional %137 %139 %140
%139 = OpLabel
%141 = OpLoad %16 %33
%142 = OpSampledImage %26 %141 %35
%143 = OpImageSampleImplicitLod %v4float %142 %30
OpBranch %138
%140 = OpLabel
%144 = OpFunctionCall %void %64 %uint_59 %uint_1 %32 %uint_0
OpBranch %138
%138 = OpLabel
%145 = OpPhi %v4float %143 %139 %116 %140
OpBranch %58
%60 = OpLabel
%115 = OpFunctionCall %void %64 %uint_59 %uint_0 %32 %55
OpBranch %58
%58 = OpLabel
%117 = OpPhi %v4float %145 %138 %116 %60
OpStore %_entryPointOutput_vColor %117
OpReturn
OpFunctionEnd
)";
const std::string output_func =
R"(%58 = OpFunction %void None %59
%60 = OpFunctionParameter %uint
%61 = OpFunctionParameter %uint
const std::string new_funcs =
R"(%40 = OpFunction %uint None %41
%42 = OpFunctionParameter %uint
%43 = OpFunctionParameter %uint
%44 = OpLabel
%50 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %42
%51 = OpLoad %uint %50
%52 = OpIAdd %uint %51 %43
%53 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %52
%54 = OpLoad %uint %53
OpReturnValue %54
OpFunctionEnd
%64 = OpFunction %void None %65
%66 = OpFunctionParameter %uint
%67 = OpFunctionParameter %uint
%68 = OpFunctionParameter %uint
%69 = OpFunctionParameter %uint
%70 = OpLabel
%74 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_0
%77 = OpAtomicIAdd %uint %74 %uint_4 %uint_0 %uint_9
%78 = OpIAdd %uint %77 %uint_9
%79 = OpArrayLength %uint %73 1
%80 = OpULessThanEqual %bool %78 %79
OpSelectionMerge %81 None
OpBranchConditional %80 %82 %81
%82 = OpLabel
%83 = OpIAdd %uint %77 %uint_0
%84 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %83
OpStore %84 %uint_9
%86 = OpIAdd %uint %77 %uint_1
%87 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %86
OpStore %87 %uint_23
%88 = OpIAdd %uint %77 %uint_2
%89 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %88
OpStore %89 %66
%91 = OpIAdd %uint %77 %uint_3
%92 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %91
OpStore %92 %uint_4
%95 = OpLoad %v4float %gl_FragCoord
%97 = OpBitcast %v4uint %95
%98 = OpCompositeExtract %uint %97 0
%99 = OpIAdd %uint %77 %uint_4
%100 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %99
OpStore %100 %98
%101 = OpCompositeExtract %uint %97 1
%103 = OpIAdd %uint %77 %uint_5
%104 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %103
OpStore %104 %101
%106 = OpIAdd %uint %77 %uint_6
%107 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %106
OpStore %107 %67
%109 = OpIAdd %uint %77 %uint_7
%110 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %109
OpStore %110 %68
%112 = OpIAdd %uint %77 %uint_8
%113 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %112
OpStore %113 %69
OpBranch %81
%81 = OpLabel
OpReturn
OpFunctionEnd
%118 = OpFunction %uint None %119
%120 = OpFunctionParameter %uint
%121 = OpFunctionParameter %uint
%122 = OpFunctionParameter %uint
%123 = OpFunctionParameter %uint
%124 = OpLabel
%125 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %120
%126 = OpLoad %uint %125
%127 = OpIAdd %uint %126 %121
%128 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %127
%129 = OpLoad %uint %128
%130 = OpIAdd %uint %129 %122
%131 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %130
%132 = OpLoad %uint %131
%133 = OpIAdd %uint %132 %123
%134 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %133
%135 = OpLoad %uint %134
OpReturnValue %135
OpFunctionEnd
)";
// SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndCheck<InstBindlessCheckPass>(
defs_before + func_before, defs_after + func_after + new_funcs, true,
true);
}
TEST_F(InstBindlessTest, NoInstrumentNonBindless) {
// This test verifies that the pass will correctly not instrument vanilla
// texture sample.
//
// Texture2D g_tColor;
//
// SamplerState g_sAniso;
//
// struct PS_INPUT
// {
// float2 vTextureCoords : TEXCOORD2;
// };
//
// struct PS_OUTPUT
// {
// float4 vColor : SV_Target0;
// };
//
// PS_OUTPUT MainPs(PS_INPUT i)
// {
// PS_OUTPUT ps_output;
// ps_output.vColor =
// g_tColor.Sample(g_sAniso, i.vTextureCoords.xy);
// return ps_output;
// }
const std::string whole_file =
R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
OpExecutionMode %MainPs OriginUpperLeft
OpSource HLSL 500
OpName %MainPs "MainPs"
OpName %g_tColor "g_tColor"
OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 0
OpDecorate %g_tColor Binding 0
OpDecorate %g_sAniso DescriptorSet 0
OpDecorate %g_sAniso Binding 0
OpDecorate %i_vTextureCoords Location 0
OpDecorate %_entryPointOutput_vColor Location 0
%void = OpTypeVoid
%8 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%v4float = OpTypeVector %float 4
%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
%14 = OpTypeSampler
%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
%16 = OpTypeSampledImage %12
%_ptr_Input_v2float = OpTypePointer Input %v2float
%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
%MainPs = OpFunction %void None %8
%19 = OpLabel
%20 = OpLoad %v2float %i_vTextureCoords
%21 = OpLoad %12 %g_tColor
%22 = OpLoad %14 %g_sAniso
%23 = OpSampledImage %16 %21 %22
%24 = OpImageSampleImplicitLod %v4float %23 %20
OpStore %_entryPointOutput_vColor %24
OpReturn
OpFunctionEnd
)";
// SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndCheck<InstBindlessCheckPass>(whole_file, whole_file, true,
true);
}
TEST_F(InstBindlessTest, InstrumentInitCheckOnScalarDescriptor) {
// This test verifies that the pass will correctly instrument vanilla
// texture sample on a scalar descriptor with an initialization check if the
// SPV_EXT_descriptor_checking extension is enabled. This is the same shader
// as NoInstrumentNonBindless, but with the extension hacked on in the SPIR-V.
const std::string defs_before =
R"(OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
OpExecutionMode %MainPs OriginUpperLeft
OpSource HLSL 500
OpName %MainPs "MainPs"
OpName %g_tColor "g_tColor"
OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 0
OpDecorate %g_tColor Binding 0
OpDecorate %g_sAniso DescriptorSet 0
OpDecorate %g_sAniso Binding 0
OpDecorate %i_vTextureCoords Location 0
OpDecorate %_entryPointOutput_vColor Location 0
%void = OpTypeVoid
%8 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%v4float = OpTypeVector %float 4
%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
%14 = OpTypeSampler
%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
%16 = OpTypeSampledImage %12
%_ptr_Input_v2float = OpTypePointer Input %v2float
%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
)";
const std::string defs_after =
R"(OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpExtension "SPV_KHR_storage_buffer_storage_class"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
OpExecutionMode %MainPs OriginUpperLeft
OpSource HLSL 500
OpName %MainPs "MainPs"
OpName %g_tColor "g_tColor"
OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 0
OpDecorate %g_tColor Binding 0
OpDecorate %g_sAniso DescriptorSet 0
OpDecorate %g_sAniso Binding 0
OpDecorate %i_vTextureCoords Location 0
OpDecorate %_entryPointOutput_vColor Location 0
OpDecorate %_runtimearr_uint ArrayStride 4
OpDecorate %_struct_35 Block
OpMemberDecorate %_struct_35 0 Offset 0
OpDecorate %37 DescriptorSet 7
OpDecorate %37 Binding 1
OpDecorate %_struct_67 Block
OpMemberDecorate %_struct_67 0 Offset 0
OpMemberDecorate %_struct_67 1 Offset 4
OpDecorate %69 DescriptorSet 7
OpDecorate %69 Binding 0
OpDecorate %gl_FragCoord BuiltIn FragCoord
%void = OpTypeVoid
%8 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%v4float = OpTypeVector %float 4
%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
%14 = OpTypeSampler
%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
%16 = OpTypeSampledImage %12
%_ptr_Input_v2float = OpTypePointer Input %v2float
%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%28 = OpTypeFunction %uint %uint %uint %uint %uint
%_runtimearr_uint = OpTypeRuntimeArray %uint
%_struct_35 = OpTypeStruct %_runtimearr_uint
%_ptr_StorageBuffer__struct_35 = OpTypePointer StorageBuffer %_struct_35
%37 = OpVariable %_ptr_StorageBuffer__struct_35 StorageBuffer
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%bool = OpTypeBool
%uint_1 = OpConstant %uint 1
%61 = OpTypeFunction %void %uint %uint %uint %uint
%_struct_67 = OpTypeStruct %uint %_runtimearr_uint
%_ptr_StorageBuffer__struct_67 = OpTypePointer StorageBuffer %_struct_67
%69 = OpVariable %_ptr_StorageBuffer__struct_67 StorageBuffer
%uint_9 = OpConstant %uint 9
%uint_4 = OpConstant %uint 4
%uint_23 = OpConstant %uint 23
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%_ptr_Input_v4float = OpTypePointer Input %v4float
%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
%v4uint = OpTypeVector %uint 4
%uint_5 = OpConstant %uint 5
%uint_6 = OpConstant %uint 6
%uint_7 = OpConstant %uint 7
%uint_8 = OpConstant %uint 8
%uint_40 = OpConstant %uint 40
%113 = OpConstantNull %v4float
)";
const std::string func_before =
R"(%MainPs = OpFunction %void None %8
%19 = OpLabel
%20 = OpLoad %v2float %i_vTextureCoords
%21 = OpLoad %12 %g_tColor
%22 = OpLoad %14 %g_sAniso
%23 = OpSampledImage %16 %21 %22
%24 = OpImageSampleImplicitLod %v4float %23 %20
OpStore %_entryPointOutput_vColor %24
OpReturn
OpFunctionEnd
)";
const std::string func_after =
R"(%MainPs = OpFunction %void None %8
%19 = OpLabel
%20 = OpLoad %v2float %i_vTextureCoords
%21 = OpLoad %12 %g_tColor
%22 = OpLoad %14 %g_sAniso
%23 = OpSampledImage %16 %21 %22
%50 = OpFunctionCall %uint %27 %uint_0 %uint_0 %uint_0 %uint_0
%52 = OpINotEqual %bool %50 %uint_0
OpSelectionMerge %54 None
OpBranchConditional %52 %55 %56
%55 = OpLabel
%57 = OpLoad %12 %g_tColor
%58 = OpSampledImage %16 %57 %22
%59 = OpImageSampleImplicitLod %v4float %58 %20
OpBranch %54
%56 = OpLabel
%112 = OpFunctionCall %void %60 %uint_40 %uint_1 %uint_0 %uint_0
OpBranch %54
%54 = OpLabel
%114 = OpPhi %v4float %59 %55 %113 %56
OpStore %_entryPointOutput_vColor %114
OpReturn
OpFunctionEnd
)";
const std::string new_funcs =
R"(%27 = OpFunction %uint None %28
%29 = OpFunctionParameter %uint
%30 = OpFunctionParameter %uint
%31 = OpFunctionParameter %uint
%32 = OpFunctionParameter %uint
%33 = OpLabel
%39 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %29
%40 = OpLoad %uint %39
%41 = OpIAdd %uint %40 %30
%42 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %41
%43 = OpLoad %uint %42
%44 = OpIAdd %uint %43 %31
%45 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %44
%46 = OpLoad %uint %45
%47 = OpIAdd %uint %46 %32
%48 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %47
%49 = OpLoad %uint %48
OpReturnValue %49
OpFunctionEnd
%60 = OpFunction %void None %61
%62 = OpFunctionParameter %uint
%63 = OpFunctionParameter %uint
%64 = OpLabel
%68 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_0
%71 = OpAtomicIAdd %uint %68 %uint_4 %uint_0 %uint_9
%72 = OpIAdd %uint %71 %uint_9
%73 = OpArrayLength %uint %67 1
%74 = OpULessThanEqual %bool %72 %73
OpSelectionMerge %75 None
OpBranchConditional %74 %76 %75
%76 = OpLabel
%77 = OpIAdd %uint %71 %uint_0
%78 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %77
OpStore %78 %uint_9
%80 = OpIAdd %uint %71 %uint_1
%81 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %80
OpStore %81 %uint_23
%82 = OpIAdd %uint %71 %uint_2
%83 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %82
OpStore %83 %60
%85 = OpIAdd %uint %71 %uint_3
%86 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %85
OpStore %86 %uint_4
%89 = OpLoad %v4float %gl_FragCoord
%91 = OpBitcast %v4uint %89
%92 = OpCompositeExtract %uint %91 0
%93 = OpIAdd %uint %71 %uint_4
%94 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %93
OpStore %94 %92
%95 = OpCompositeExtract %uint %91 1
%97 = OpIAdd %uint %71 %uint_5
%98 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %97
OpStore %98 %95
%100 = OpIAdd %uint %71 %uint_6
%101 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %100
OpStore %101 %61
%103 = OpIAdd %uint %71 %uint_7
%104 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %103
OpStore %104 %62
%106 = OpIAdd %uint %71 %uint_8
%107 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %106
OpStore %107 %63
OpBranch %75
%75 = OpLabel
%64 = OpFunctionParameter %uint
%65 = OpFunctionParameter %uint
%66 = OpLabel
%70 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0
%73 = OpAtomicIAdd %uint %70 %uint_4 %uint_0 %uint_9
%74 = OpIAdd %uint %73 %uint_9
%75 = OpArrayLength %uint %69 1
%76 = OpULessThanEqual %bool %74 %75
OpSelectionMerge %77 None
OpBranchConditional %76 %78 %77
%78 = OpLabel
%79 = OpIAdd %uint %73 %uint_0
%80 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %79
OpStore %80 %uint_9
%82 = OpIAdd %uint %73 %uint_1
%83 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %82
OpStore %83 %uint_23
%85 = OpIAdd %uint %73 %uint_2
%86 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %85
OpStore %86 %62
%88 = OpIAdd %uint %73 %uint_3
%89 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %88
OpStore %89 %uint_4
%92 = OpLoad %v4float %gl_FragCoord
%94 = OpBitcast %v4uint %92
%95 = OpCompositeExtract %uint %94 0
%96 = OpIAdd %uint %73 %uint_4
%97 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %96
OpStore %97 %95
%98 = OpCompositeExtract %uint %94 1
%100 = OpIAdd %uint %73 %uint_5
%101 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %100
OpStore %101 %98
%103 = OpIAdd %uint %73 %uint_6
%104 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %103
OpStore %104 %63
%106 = OpIAdd %uint %73 %uint_7
%107 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %106
OpStore %107 %64
%109 = OpIAdd %uint %73 %uint_8
%110 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %109
OpStore %110 %65
OpBranch %77
%77 = OpLabel
OpReturn
OpFunctionEnd
)";
// SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndCheck<InstBindlessCheckPass>(
defs_before + func_before, defs_after + func_after + output_func, true,
defs_before + func_before, defs_after + func_after + new_funcs, true,
true);
}

View File

@ -20,6 +20,7 @@ add_spvtools_unittest(TARGET reduce
reduce_test_util.cpp
reduce_test_util.h
reducer_test.cpp
remove_block_test.cpp
remove_function_test.cpp
remove_opname_instruction_test.cpp
remove_unreferenced_instruction_test.cpp

View File

@ -229,8 +229,12 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
spvtools::ValidatorOptions validator_options;
reducer.Run(std::move(binary_in), &binary_out, reducer_options);
Reducer::ReductionResultStatus status = reducer.Run(
std::move(binary_in), &binary_out, reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
CheckEqual(env, expected, binary_out);
}
@ -255,7 +259,7 @@ TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
%10 = OpLabel
%3 = OpVariable %8 Function
%4 = OpLoad %7 %3
OpStore %3 %7
OpStore %3 %9
OpReturn
OpFunctionEnd
)";
@ -280,7 +284,7 @@ TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
Reducer reducer(env);
// Make ping-pong interesting very quickly, as there are not much
// Make ping-pong interesting very quickly, as there are not many
// opportunities.
PingPongInteresting ping_pong_interesting(1);
reducer.SetMessageConsumer(NopDiagnostic);
@ -300,12 +304,16 @@ TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
spvtools::ValidatorOptions validator_options;
reducer.Run(std::move(binary_in), &binary_out, reducer_options);
Reducer::ReductionResultStatus status = reducer.Run(
std::move(binary_in), &binary_out, reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
CheckEqual(env, expected, binary_out);
}
} // namespace
} // namespace reduce
} // namespace spvtools
} // namespace spvtools

View File

@ -0,0 +1,358 @@
// Copyright (c) 2019 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 "reduce_test_util.h"
#include "source/opt/build_module.h"
#include "source/reduce/reduction_opportunity.h"
#include "source/reduce/remove_block_reduction_opportunity.h"
#include "source/reduce/remove_block_reduction_opportunity_finder.h"
namespace spvtools {
namespace reduce {
namespace {
TEST(RemoveBlockReductionPassTest, BasicCheck) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "x"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 1
%10 = OpConstant %6 2
%11 = OpConstant %6 3
%12 = OpConstant %6 4
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
OpBranch %14
%13 = OpLabel ; unreachable
OpStore %8 %9
OpBranch %14
%14 = OpLabel
OpStore %8 %10
OpBranch %16
%15 = OpLabel ; unreachable
OpStore %8 %11
OpBranch %16
%16 = OpLabel
OpStore %8 %12
OpBranch %17
%17 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
const auto ops =
RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(2, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
std::string after_op_0 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "x"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 1
%10 = OpConstant %6 2
%11 = OpConstant %6 3
%12 = OpConstant %6 4
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
OpBranch %14
%14 = OpLabel
OpStore %8 %10
OpBranch %16
%15 = OpLabel
OpStore %8 %11
OpBranch %16
%16 = OpLabel
OpStore %8 %12
OpBranch %17
%17 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(env, after_op_0, context.get());
ASSERT_TRUE(ops[1]->PreconditionHolds());
ops[1]->TryToApply();
std::string after_op_1 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "x"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 1
%10 = OpConstant %6 2
%11 = OpConstant %6 3
%12 = OpConstant %6 4
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
OpBranch %14
%14 = OpLabel
OpStore %8 %10
OpBranch %16
%16 = OpLabel
OpStore %8 %12
OpBranch %17
%17 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(env, after_op_1, context.get());
}
TEST(RemoveBlockReductionPassTest, UnreachableContinueAndMerge) {
// Loop with unreachable merge and continue target. There should be no
// opportunities.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %13
%13 = OpLabel
OpLoopMerge %16 %15 None
OpBranch %14
%14 = OpLabel
OpReturn
%15 = OpLabel
OpBranch %13
%16 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
const auto ops =
RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(0, ops.size());
}
TEST(RemoveBlockReductionPassTest, OneBlock) {
// Function with just one block. There should be no opportunities.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
const auto ops =
RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(0, ops.size());
}
TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithOutsideIdUses) {
// A function with two unreachable blocks A -> B. A defines ID %9 and B uses
// %9. There are no references to A, but removing A would be invalid because
// of B's use of %9, so there should be no opportunities.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeInt 32 1
%5 = OpTypeFunction %3
%6 = OpConstant %4 1
%2 = OpFunction %3 None %5
%7 = OpLabel
OpReturn
%8 = OpLabel ; A
%9 = OpUndef %4
OpBranch %10
%10 = OpLabel ; B
%11 = OpIAdd %4 %6 %9 ; uses %9 from A, so A cannot be removed
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
const auto ops =
RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(0, ops.size());
}
TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) {
// Similar to the above test.
// A function with two unreachable blocks A -> B. Both blocks create and use
// IDs, but the uses are contained within each block, so A should be removed.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeInt 32 1
%5 = OpTypeFunction %3
%6 = OpConstant %4 1
%2 = OpFunction %3 None %5
%7 = OpLabel
OpReturn
%8 = OpLabel ; A
%9 = OpUndef %4 ; define %9
%10 = OpIAdd %4 %6 %9 ; use %9
OpBranch %11
%11 = OpLabel ; B
%12 = OpUndef %4 ; define %12
%13 = OpIAdd %4 %6 %12 ; use %12
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
// Same as above, but block A is removed.
std::string after_op_0 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeInt 32 1
%5 = OpTypeFunction %3
%6 = OpConstant %4 1
%2 = OpFunction %3 None %5
%7 = OpLabel
OpReturn
%11 = OpLabel
%12 = OpUndef %4
%13 = OpIAdd %4 %6 %12
OpReturn
OpFunctionEnd
)";
CheckEqual(env, after_op_0, context.get());
// Find opportunities again. There are no reference to B. B should now be
// removed.
ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
// Same as above, but block B is removed.
std::string after_op_0_again = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeInt 32 1
%5 = OpTypeFunction %3
%6 = OpConstant %4 1
%2 = OpFunction %3 None %5
%7 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(env, after_op_0_again, context.get());
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@ -52,6 +52,65 @@ class BlindlyRemoveGlobalValuesReductionOpportunityFinder
}
};
// A dumb reduction opportunity that exists at the start of every function whose
// first instruction is an OpVariable instruction. When applied, the OpVariable
// instruction is duplicated (with a fresh result id). This allows each
// reduction step to increase the number of variables to check if the validator
// limits are enforced.
class OpVariableDuplicatorReductionOpportunity : public ReductionOpportunity {
public:
OpVariableDuplicatorReductionOpportunity(Function* function_)
: function_(function_) {}
bool PreconditionHolds() override {
Instruction* first_instruction = &*function_->begin()[0].begin();
return first_instruction->opcode() == SpvOpVariable;
}
protected:
void Apply() override {
// Duplicate the first OpVariable instruction.
Instruction* first_instruction = &*function_->begin()[0].begin();
assert(first_instruction->opcode() == SpvOpVariable &&
"Expected first instruction to be OpVariable");
IRContext* context = first_instruction->context();
Instruction* cloned_instruction = first_instruction->Clone(context);
cloned_instruction->SetResultId(context->TakeNextId());
cloned_instruction->InsertBefore(first_instruction);
}
private:
Function* function_;
};
// A reduction opportunity finder that finds
// OpVariableDuplicatorReductionOpportunity.
class OpVariableDuplicatorReductionOpportunityFinder
: public ReductionOpportunityFinder {
public:
OpVariableDuplicatorReductionOpportunityFinder() = default;
~OpVariableDuplicatorReductionOpportunityFinder() override = default;
std::string GetName() const final {
return "LocalVariableAdderReductionOpportunityFinder";
};
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const final {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
for (auto& function : *context->module()) {
Instruction* first_instruction = &*function.begin()[0].begin();
if (first_instruction->opcode() == SpvOpVariable) {
result.push_back(
MakeUnique<OpVariableDuplicatorReductionOpportunity>(&function));
}
}
return result;
}
};
TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
// A module whose global values are all referenced, so that any application of
// MakeModuleInvalidPass will make the module invalid.
@ -160,8 +219,12 @@ TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
spvtools::ValidatorOptions validator_options;
reducer.Run(std::move(binary_in), &binary_out, reducer_options);
Reducer::ReductionResultStatus status = reducer.Run(
std::move(binary_in), &binary_out, reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
// The reducer should have no impact.
CheckEqual(env, original, binary_out);
@ -365,11 +428,145 @@ TEST(ValidationDuringReductionTest, CheckNotAlwaysInvalidCanMakeProgress) {
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
spvtools::ValidatorOptions validator_options;
Reducer::ReductionResultStatus status = reducer.Run(
std::move(binary_in), &binary_out, reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
reducer.Run(std::move(binary_in), &binary_out, reducer_options);
CheckEqual(env, expected, binary_out);
}
// Sets up a Reducer for use in the CheckValidationOptions test; avoids
// repetition.
void setupReducerForCheckValidationOptions(Reducer* reducer) {
reducer->SetMessageConsumer(NopDiagnostic);
// Say that every module is interesting.
reducer->SetInterestingnessFunction(
[](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
// Each "reduction" step will duplicate the first OpVariable instruction in
// the function.
reducer->AddReductionPass(
MakeUnique<OpVariableDuplicatorReductionOpportunityFinder>());
}
TEST(ValidationDuringReductionTest, CheckValidationOptions) {
// A module that only validates when the "skip-block-layout" validator option
// is used. Also, the entry point's first instruction creates a local
// variable; this instruction will be duplicated on each reduction step.
std::string original = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %2 "Main" %3
OpSource HLSL 600
OpDecorate %3 BuiltIn Position
OpDecorate %4 DescriptorSet 0
OpDecorate %4 Binding 99
OpDecorate %5 ArrayStride 16
OpMemberDecorate %6 0 Offset 0
OpMemberDecorate %6 1 Offset 32
OpMemberDecorate %6 1 MatrixStride 16
OpMemberDecorate %6 1 ColMajor
OpMemberDecorate %6 2 Offset 96
OpMemberDecorate %6 3 Offset 100
OpMemberDecorate %6 4 Offset 112
OpMemberDecorate %6 4 MatrixStride 16
OpMemberDecorate %6 4 ColMajor
OpMemberDecorate %6 5 Offset 176
OpDecorate %6 Block
%7 = OpTypeFloat 32
%8 = OpTypeVector %7 4
%9 = OpTypeMatrix %8 4
%10 = OpTypeVector %7 2
%11 = OpTypeInt 32 1
%12 = OpTypeInt 32 0
%13 = OpConstant %12 2
%14 = OpConstant %11 1
%15 = OpConstant %11 5
%5 = OpTypeArray %8 %13
%6 = OpTypeStruct %5 %9 %12 %10 %9 %7
%16 = OpTypePointer Uniform %6
%17 = OpTypePointer Output %8
%18 = OpTypeVoid
%19 = OpTypeFunction %18
%20 = OpTypePointer Uniform %7
%4 = OpVariable %16 Uniform
%3 = OpVariable %17 Output
%21 = OpTypePointer Function %11
%2 = OpFunction %18 None %19
%22 = OpLabel
%23 = OpVariable %21 Function
%24 = OpAccessChain %20 %4 %15
%25 = OpLoad %7 %24
%26 = OpCompositeConstruct %8 %25 %25 %25 %25
OpStore %3 %26
OpReturn
OpFunctionEnd
)";
spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
spvtools::ValidatorOptions validator_options;
reducer_options.set_step_limit(3);
// Reduction should fail because the initial state is invalid without the
// "skip-block-layout" validator option. Note that the interestingness test
// always returns true.
{
Reducer reducer(env);
setupReducerForCheckValidationOptions(&reducer);
Reducer::ReductionResultStatus status =
reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kInitialStateInvalid);
}
// Try again with validator option.
validator_options.SetSkipBlockLayout(true);
// Reduction should hit step limit; module is seen as valid, interestingness
// test always succeeds, and the finder yields infinite opportunities.
{
Reducer reducer(env);
setupReducerForCheckValidationOptions(&reducer);
Reducer::ReductionResultStatus status =
reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kReachedStepLimit);
}
// Now set a limit on the number of local variables.
validator_options.SetUniversalLimit(spv_validator_limit_max_local_variables,
2);
// Reduction should "complete"; after one step, a local variable is added and
// the module becomes "invalid" given the validator limits.
{
Reducer reducer(env);
setupReducerForCheckValidationOptions(&reducer);
Reducer::ReductionResultStatus status =
reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
}
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@ -4452,6 +4452,38 @@ OpExtension "SPV_KHR_vulkan_memory_model"
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}
// This example used to cause a seg fault on OpReturnValue, verifying it doesn't
// anymore.
TEST_F(ValidateImage, Issue2463NoSegFault) {
const std::string spirv = R"(
OpCapability Linkage
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%6 = OpTypeFunction %void
%float = OpTypeFloat 32
%8 = OpTypeImage %float 3D 0 0 0 1 Unknown
%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
%10 = OpTypeSampler
%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
%12 = OpTypeSampledImage %8
%13 = OpTypeFunction %12 %_ptr_UniformConstant_8 %_ptr_UniformConstant_10
%23 = OpFunction %12 None %13
%24 = OpFunctionParameter %_ptr_UniformConstant_8
%25 = OpFunctionParameter %_ptr_UniformConstant_10
%26 = OpLabel
%27 = OpLoad %8 %24
%28 = OpLoad %10 %25
%29 = OpSampledImage %12 %27 %28
OpReturnValue %29
OpFunctionEnd
)";
CompileSuccessfully(spirv);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@ -110,6 +110,13 @@ Options (in lexicographical order):
reducer will take before giving up.
--version
Display reducer version information.
Supported validator options are as follows. See `spirv-val --help` for details.
--relax-logical-pointer
--relax-block-layout
--scalar-block-layout
--skip-block-layout
--relax-struct-store
)",
program, program);
}
@ -127,7 +134,8 @@ void ReduceDiagnostic(spv_message_level_t level, const char* /*source*/,
ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
const char** interestingness_test,
spvtools::ReducerOptions* reducer_options) {
spvtools::ReducerOptions* reducer_options,
spvtools::ValidatorOptions* validator_options) {
uint32_t positional_arg_index = 0;
for (int argi = 1; argi < argc; ++argi) {
@ -164,6 +172,16 @@ ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
assert(!*interestingness_test);
*interestingness_test = cur_arg;
positional_arg_index++;
} else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
validator_options->SetRelaxLogicalPointer(true);
} else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
validator_options->SetRelaxBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
validator_options->SetScalarBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
validator_options->SetSkipBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
validator_options->SetRelaxStructStore(true);
} else {
spvtools::Error(ReduceDiagnostic, nullptr, {},
"Too many positional arguments specified");
@ -195,9 +213,10 @@ int main(int argc, const char** argv) {
spv_target_env target_env = kDefaultEnvironment;
spvtools::ReducerOptions reducer_options;
spvtools::ValidatorOptions validator_options;
ReduceStatus status =
ParseFlags(argc, argv, &in_file, &interestingness_test, &reducer_options);
ReduceStatus status = ParseFlags(argc, argv, &in_file, &interestingness_test,
&reducer_options, &validator_options);
if (status.action == REDUCE_STOP) {
return status.code;
@ -255,8 +274,8 @@ int main(int argc, const char** argv) {
}
std::vector<uint32_t> binary_out;
const auto reduction_status =
reducer.Run(std::move(binary_in), &binary_out, reducer_options);
const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
reducer_options, validator_options);
if (reduction_status ==
Reducer::ReductionResultStatus::kInitialStateNotInteresting ||