aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/spirv-tools/instrument.hpp32
-rw-r--r--include/spirv-tools/optimizer.hpp22
-rw-r--r--source/opt/inst_bindless_check_pass.cpp388
-rw-r--r--source/opt/inst_bindless_check_pass.h115
-rw-r--r--source/opt/instrument_pass.cpp181
-rw-r--r--source/opt/instrument_pass.h39
-rw-r--r--source/opt/optimizer.cpp9
-rw-r--r--test/opt/inst_bindless_check_test.cpp522
8 files changed, 966 insertions, 342 deletions
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index 01c7c6d1..9711b929 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -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;
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index bbc70041..08ef5e67 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -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.
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index d9f221f9..47c81347 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -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;
- }
- 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;
+ 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;
}
- // 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;
}
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
index be782cef..79c34f1f 100644
--- a/source/opt/inst_bindless_check_pass.h
+++ b/source/opt/inst_bindless_check_pass.h
@@ -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_;
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index 5c26b5c7..668c9803 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -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());
- }
- 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) {
+ module_offset += 1;
+ module_offset += static_cast<uint32_t>(i.dbg_line_insts().size());
+ }
+
+ 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;
}
}
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index 003e6474..c4b97d63 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -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_;
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 5fb48757..d4499a88 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -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() {
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
index 65dd7800..deea1edc 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -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
+%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 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
-%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
+%114 = OpPhi %v4float %59 %55 %113 %56
+OpStore %_entryPointOutput_vColor %114
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"(%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);
}