Add base and core bindless validation instrumentation classes (#2014)
* Add base and core bindless validation instrumentation classes
* Fix formatting.
* Few more formatting fixes
* Fix build failure
* More build fixes
* Need to call non-const functions in order.
Specifically, these are functions which call TakeNextId(). These need to
be called in a specific order to guarantee that tests which do exact
compares will work across all platforms. c++ pretty much does not
guarantee order of evaluation of operands, so any such functions need to
be called separately in individual statements to guarantee order.
* More ordering.
* And more ordering.
* And more formatting.
* Attempt to fix NDK build
* Another attempt to address NDK build problem.
* One more attempt at NDK build failure
* Add instrument.hpp to BUILD.gn
* Some name improvement in instrument.hpp
* Change all types in instrument.hpp to int.
* Improve documentation in instrument.hpp
* Format fixes
* Comment clean up in instrument.hpp
* imageInst -> image_inst
* Fix GetLabel() issue.
diff --git a/Android.mk b/Android.mk
index 4e6025d..79a79f6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -102,8 +102,10 @@
source/opt/inline_pass.cpp \
source/opt/inline_exhaustive_pass.cpp \
source/opt/inline_opaque_pass.cpp \
+ source/opt/inst_bindless_check_pass.cpp \
source/opt/instruction.cpp \
source/opt/instruction_list.cpp \
+ source/opt/instrument_pass.cpp \
source/opt/ir_context.cpp \
source/opt/ir_loader.cpp \
source/opt/licm_pass.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index cf6286f..5a5b476 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -297,6 +297,7 @@
"include/spirv-tools/libspirv.hpp",
"include/spirv-tools/linker.hpp",
"include/spirv-tools/optimizer.hpp",
+ "include/spirv-tools/instrument.hpp",
]
public_configs = [ ":spvtools_public_config" ]
@@ -511,10 +512,14 @@
"source/opt/inline_opaque_pass.h",
"source/opt/inline_pass.cpp",
"source/opt/inline_pass.h",
+ "source/opt/inst_bindless_check_pass.cpp",
+ "source/opt/inst_bindless_check_pass.h",
"source/opt/instruction.cpp",
"source/opt/instruction.h",
"source/opt/instruction_list.cpp",
"source/opt/instruction_list.h",
+ "source/opt/instrument_pass.cpp",
+ "source/opt/instrument_pass.h",
"source/opt/ir_builder.h",
"source/opt/ir_context.cpp",
"source/opt/ir_context.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5872f92..8143123 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -239,6 +239,7 @@
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/optimizer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/linker.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/instrument.hpp
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/spirv-tools/)
endif(ENABLE_SPIRV_TOOLS_INSTALL)
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
new file mode 100644
index 0000000..69d1ad2
--- /dev/null
+++ b/include/spirv-tools/instrument.hpp
@@ -0,0 +1,135 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG Inc.
+//
+// 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 INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
+#define INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
+
+// Shader Instrumentation Interface
+//
+// This file provides an external interface for applications that wish to
+// communicate with shaders instrumented by passes created by:
+//
+// CreateInstBindlessCheckPass
+//
+// More detailed documentation of this routine can be found in optimizer.hpp
+
+namespace spvtools {
+
+// Stream Output Buffer Offsets
+//
+// The following values provide 32-bit word offsets into the output buffer
+// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
+// by InstBindlessCheckPass.
+//
+// The first word of the debug output buffer contains the next available word
+// in the data stream to be written. Shaders will atomically read and update
+// this value so as not to overwrite each others records. This value must be
+// initialized to zero
+static const int kDebugOutputSizeOffset = 0;
+
+// The second word of the output buffer is the start of the stream of records
+// written by the instrumented shaders. Each record represents a validation
+// error. The format of the records is documented below.
+static const int kDebugOutputDataOffset = 1;
+
+// Common Stream Record Offsets
+//
+// The following are offsets to fields which are common to all records written
+// to the output stream.
+//
+// Each record first contains the size of the record in 32-bit words, including
+// the size word.
+static const int kInstCommonOutSize = 0;
+
+// This is the shader id passed by the layer when the instrumentation pass is
+// created.
+static const int kInstCommonOutShaderId = 1;
+
+// This is the ordinal position of the instruction within the SPIR-V shader
+// which generated the validation error.
+static const int kInstCommonOutInstructionIdx = 2;
+
+// This is the stage which generated the validation error. This word is used
+// to determine the contents of the next two words in the record.
+// 0:Vert, 1:TessCtrl, 2:TessEval, 3:Geom, 4:Frag, 5:Compute
+static const int kInstCommonOutStageIdx = 3;
+static const int kInstCommonOutCnt = 4;
+
+// Stage-specific Stream Record Offsets
+//
+// Each stage will contain different values in the next two words of the record
+// used to identify which instantiation of the shader generated the validation
+// error.
+//
+// Vertex Shader Output Record Offsets
+static const int kInstVertOutVertexId = kInstCommonOutCnt;
+static const int kInstVertOutInstanceId = kInstCommonOutCnt + 1;
+
+// Frag Shader Output Record Offsets
+static const int kInstFragOutFragCoordX = kInstCommonOutCnt;
+static const int kInstFragOutFragCoordY = kInstCommonOutCnt + 1;
+
+// Compute Shader Output Record Offsets
+static const int kInstCompOutGlobalInvocationId = kInstCommonOutCnt;
+static const int kInstCompOutUnused = kInstCommonOutCnt + 1;
+
+// Tessellation Shader Output Record Offsets
+static const int kInstTessOutInvocationId = kInstCommonOutCnt;
+static const int kInstTessOutUnused = kInstCommonOutCnt + 1;
+
+// Geometry Shader Output Record Offsets
+static const int kInstGeomOutPrimitiveId = kInstCommonOutCnt;
+static const int kInstGeomOutInvocationId = kInstCommonOutCnt + 1;
+
+// Size of Common and Stage-specific Members
+static const int kInstStageOutCnt = kInstCommonOutCnt + 2;
+
+// Validation Error Code
+//
+// This identifies the validation error. It also helps to identify
+// how many words follow in the record and their meaning.
+static const int kInstValidationOutError = kInstStageOutCnt;
+
+// Validation-specific Output Record Offsets
+//
+// Each different validation will generate a potentially different
+// number of words at the end of the record giving more specifics
+// about the validation error.
+//
+// A bindless bounds error will output the index and the bound.
+static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2;
+static const int kInstBindlessOutCnt = kInstStageOutCnt + 3;
+
+// Maximum Output Record Member Count
+static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
+
+// Validation Error Codes
+//
+// These are the possible validation error codes.
+static const int kInstErrorBindlessBounds = 0;
+
+// Debug Buffer Bindings
+//
+// These are the bindings for the different buffers which are
+// read or written by the instrumentation passes.
+//
+// This is the output buffer written by InstBindlessCheckPass.
+static const int kDebugOutputBindingStream = 0;
+
+} // namespace spvtools
+
+#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 5a3c52f..6f08efd 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -657,6 +657,32 @@
// them into a single instruction where possible.
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.
+//
+// Dead code elimination should be run after this pass as the original,
+// potentially invalid code is not removed and could cause undefined behavior,
+// including crashes. It may also be beneficial to run Simplification
+// (ie Constant Propagation), DeadBranchElim and BlockMerge after this pass to
+// optimize instrument code involving the testing of compile-time constants.
+// It is also generally recommended that this pass (and all
+// instrumentation passes) be run after any legalization and optimization
+// passes. This will give better analysis for the instrumentation and avoid
+// potentially de-optimizing the instrument code, for example, inlining
+// the debug record output function throughout the module.
+//
+// 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.
+//
+// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
+Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
+ uint32_t shader_id);
+
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index eac956c..3ab2606 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -46,8 +46,10 @@
inline_exhaustive_pass.h
inline_opaque_pass.h
inline_pass.h
+ inst_bindless_check_pass.h
instruction.h
instruction_list.h
+ instrument_pass.h
ir_builder.h
ir_context.h
ir_loader.h
@@ -134,8 +136,10 @@
inline_exhaustive_pass.cpp
inline_opaque_pass.cpp
inline_pass.cpp
+ inst_bindless_check_pass.cpp
instruction.cpp
instruction_list.cpp
+ instrument_pass.cpp
ir_context.cpp
ir_loader.cpp
licm_pass.cpp
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index 0971092..ff3a412 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -71,6 +71,9 @@
// Appends all of block's instructions (except label) to this block
inline void AddInstructions(BasicBlock* bp);
+ // The pointer to the label starting this basic block.
+ std::unique_ptr<Instruction>& GetLabel() { return label_; }
+
// The label starting this basic block.
Instruction* GetLabelInst() { return label_.get(); }
const Instruction* GetLabelInst() const { return label_.get(); }
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
index 185dcb7..9990661 100644
--- a/source/opt/decoration_manager.cpp
+++ b/source/opt/decoration_manager.cpp
@@ -261,6 +261,7 @@
AddDecoration(&inst);
}
}
+
void DecorationManager::AddDecoration(Instruction* inst) {
switch (inst->opcode()) {
case SpvOpDecorate:
@@ -289,6 +290,43 @@
}
}
+void DecorationManager::AddDecoration(SpvOp opcode,
+ std::vector<Operand> opnds) {
+ IRContext* ctx = module_->context();
+ std::unique_ptr<Instruction> newDecoOp(
+ new Instruction(ctx, opcode, 0, 0, opnds));
+ ctx->AddAnnotationInst(std::move(newDecoOp));
+}
+
+void DecorationManager::AddDecoration(uint32_t inst_id, uint32_t decoration) {
+ AddDecoration(
+ SpvOpDecorate,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}}});
+}
+
+void DecorationManager::AddDecorationVal(uint32_t inst_id, uint32_t decoration,
+ uint32_t decoration_value) {
+ AddDecoration(
+ SpvOpDecorate,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+ {decoration_value}}});
+}
+
+void DecorationManager::AddMemberDecoration(uint32_t inst_id, uint32_t member,
+ uint32_t decoration,
+ uint32_t decoration_value) {
+ AddDecoration(
+ SpvOpMemberDecorate,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {member}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+ {decoration_value}}});
+}
+
template <typename T>
std::vector<T> DecorationManager::InternalGetDecorationsFor(
uint32_t id, bool include_linkage) {
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
index fb9cfb6..2d87f5f 100644
--- a/source/opt/decoration_manager.h
+++ b/source/opt/decoration_manager.h
@@ -111,6 +111,20 @@
// Informs the decoration manager of a new decoration that it needs to track.
void AddDecoration(Instruction* inst);
+ // Add decoration with |opcode| and operands |opnds|.
+ void AddDecoration(SpvOp opcode, const std::vector<Operand> opnds);
+
+ // Add |decoration| of |inst_id| to module.
+ void AddDecoration(uint32_t inst_id, uint32_t decoration);
+
+ // Add |decoration, decoration_value| of |inst_id| to module.
+ void AddDecorationVal(uint32_t inst_id, uint32_t decoration,
+ uint32_t decoration_value);
+
+ // Add |decoration, decoration_value| of |inst_id, member| to module.
+ void AddMemberDecoration(uint32_t member, uint32_t inst_id,
+ uint32_t decoration, uint32_t decoration_value);
+
private:
// Analyzes the defs and uses in the given |module| and populates data
// structures in this class. Does nothing if |module| is nullptr.
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
index 54f284c..0ec98ca 100644
--- a/source/opt/def_use_manager.cpp
+++ b/source/opt/def_use_manager.cpp
@@ -279,6 +279,16 @@
}
if (lhs.inst_to_used_ids_ != rhs.inst_to_used_ids_) {
+ for (auto p : lhs.inst_to_used_ids_) {
+ if (rhs.inst_to_used_ids_.count(p.first) == 0) {
+ return false;
+ }
+ }
+ for (auto p : rhs.inst_to_used_ids_) {
+ if (lhs.inst_to_used_ids_.count(p.first) == 0) {
+ return false;
+ }
+ }
return false;
}
return true;
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
new file mode 100644
index 0000000..1901f76
--- /dev/null
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -0,0 +1,263 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG Inc.
+//
+// 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 "inst_bindless_check_pass.h"
+
+namespace {
+
+// Input Operand Indices
+static const int kSpvImageSampleImageIdInIdx = 0;
+static const int kSpvSampledImageImageIdInIdx = 0;
+static const int kSpvSampledImageSamplerIdInIdx = 1;
+static const int kSpvImageSampledImageIdInIdx = 0;
+static const int kSpvLoadPtrIdInIdx = 0;
+static const int kSpvAccessChainBaseIdInIdx = 0;
+static const int kSpvAccessChainIndex0IdInIdx = 1;
+static const int kSpvTypePointerTypeIdInIdx = 1;
+static const int kSpvTypeArrayLengthIdInIdx = 1;
+static const int kSpvConstantValueInIdx = 0;
+
+} // anonymous namespace
+
+namespace spvtools {
+namespace opt {
+
+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()) {
+ case SpvOp::SpvOpImageSampleImplicitLod:
+ case SpvOp::SpvOpImageSampleExplicitLod:
+ case SpvOp::SpvOpImageSampleDrefImplicitLod:
+ case SpvOp::SpvOpImageSampleDrefExplicitLod:
+ case SpvOp::SpvOpImageSampleProjImplicitLod:
+ case SpvOp::SpvOpImageSampleProjExplicitLod:
+ case SpvOp::SpvOpImageSampleProjDrefImplicitLod:
+ case SpvOp::SpvOpImageSampleProjDrefExplicitLod:
+ case SpvOp::SpvOpImageGather:
+ case SpvOp::SpvOpImageDrefGather:
+ case SpvOp::SpvOpImageQueryLod:
+ case SpvOp::SpvOpImageSparseSampleImplicitLod:
+ case SpvOp::SpvOpImageSparseSampleExplicitLod:
+ case SpvOp::SpvOpImageSparseSampleDrefImplicitLod:
+ case SpvOp::SpvOpImageSparseSampleDrefExplicitLod:
+ case SpvOp::SpvOpImageSparseSampleProjImplicitLod:
+ case SpvOp::SpvOpImageSparseSampleProjExplicitLod:
+ case SpvOp::SpvOpImageSparseSampleProjDrefImplicitLod:
+ case SpvOp::SpvOpImageSparseSampleProjDrefExplicitLod:
+ case SpvOp::SpvOpImageSparseGather:
+ case SpvOp::SpvOpImageSparseDrefGather:
+ case SpvOp::SpvOpImageFetch:
+ case SpvOp::SpvOpImageRead:
+ case SpvOp::SpvOpImageQueryFormat:
+ case SpvOp::SpvOpImageQueryOrder:
+ case SpvOp::SpvOpImageQuerySizeLod:
+ case SpvOp::SpvOpImageQuerySize:
+ case SpvOp::SpvOpImageQueryLevels:
+ case SpvOp::SpvOpImageQuerySamples:
+ case SpvOp::SpvOpImageSparseFetch:
+ case SpvOp::SpvOpImageSparseRead:
+ case SpvOp::SpvOpImageWrite:
+ image_id =
+ ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
+ break;
+ default:
+ return;
+ }
+ Instruction* image_inst = get_def_use_mgr()->GetDef(image_id);
+ uint32_t load_id;
+ Instruction* load_inst;
+ if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
+ load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
+ load_inst = get_def_use_mgr()->GetDef(load_id);
+ } else if (image_inst->opcode() == SpvOp::SpvOpImage) {
+ load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
+ load_inst = get_def_use_mgr()->GetDef(load_id);
+ } else {
+ load_id = image_id;
+ load_inst = image_inst;
+ image_id = 0;
+ }
+ if (load_inst->opcode() != SpvOp::SpvOpLoad) {
+ // TODO(greg-lunarg): Handle additional possibilities
+ return;
+ }
+ 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);
+ // TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
+ // out of debug input buffer.
+ if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
+ // If index and bound both compile-time constants and index < bound,
+ // return without changing
+ uint32_t 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;
+ // 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);
+ InstructionBuilder builder(
+ context(), &*new_blk_ptr,
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
+ Instruction* ult_inst =
+ builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
+ 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)));
+ 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
+ (void)builder.AddBranch(merge_blk_id);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ 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,
+ {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
+ (void)builder.AddBranch(merge_blk_id);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ 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.
+ 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()->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));
+}
+
+void InstBindlessCheckPass::InitializeInstBindlessCheck() {
+ // Initialize base class
+ InitializeInstrument();
+ // Look for related extensions
+ ext_descriptor_indexing_defined_ = false;
+ for (auto& ei : get_module()->extensions()) {
+ const char* ext_name =
+ reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
+ if (strcmp(ext_name, "SPV_EXT_descriptor_indexing") == 0) {
+ ext_descriptor_indexing_defined_ = true;
+ break;
+ }
+ }
+}
+
+Pass::Status InstBindlessCheckPass::ProcessImpl() {
+ // Perform instrumentation 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,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+ return GenBindlessCheckCode(ref_inst_itr, ref_block_itr,
+ instruction_idx, stage_idx, new_blocks);
+ };
+ bool modified = InstProcessEntryPointCallTree(pfn);
+ // This pass does not update inst->blk info
+ context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping);
+ return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+Pass::Status InstBindlessCheckPass::Process() {
+ InitializeInstBindlessCheck();
+ return ProcessImpl();
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
new file mode 100644
index 0000000..eaa1dd2
--- /dev/null
+++ b/source/opt/inst_bindless_check_pass.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG Inc.
+//
+// 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 LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
+#define LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
+
+#include "instrument_pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This class/pass is designed to support the bindless (descriptor indexing)
+// GPU-assisted validation layer of
+// https://github.com/KhronosGroup/Vulkan-ValidationLayers. Its internal and
+// external design may change as the layer evolves.
+class InstBindlessCheckPass : public InstrumentPass {
+ public:
+ // For test harness only
+ InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
+ // For all other interfaces
+ InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
+ : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
+
+ ~InstBindlessCheckPass() = default;
+
+ // See optimizer.hpp for pass user documentation.
+ Status Process() override;
+
+ const char* name() const override { return "inst-bindless-check-pass"; }
+
+ private:
+ // 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
+ // 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
+ // output buffer stream including |function_idx, instruction_idx, stage_idx|
+ // and replace 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
+ // error records. The validation-specific part of the error record will
+ // have the format:
+ //
+ // Validation Error Code (=kInstErrorBindlessBounds)
+ // Descriptor Index
+ // Descriptor Array Size
+ //
+ // The Descriptor Index is the index which has been determined to be
+ // out-of-bounds.
+ //
+ // 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);
+
+ Pass::Status ProcessImpl();
+
+ // True if VK_EXT_descriptor_indexing is defined
+ bool ext_descriptor_indexing_defined_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
new file mode 100644
index 0000000..3291bbb
--- /dev/null
+++ b/source/opt/instrument_pass.cpp
@@ -0,0 +1,710 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG Inc.
+//
+// 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 "instrument_pass.h"
+
+#include "source/cfa.h"
+
+namespace {
+
+// Common Parameter Positions
+static const int kInstCommonParamInstIdx = 0;
+static const int kInstCommonParamCnt = 1;
+
+// Indices of operands in SPIR-V instructions
+static const int kEntryPointExecutionModelInIdx = 0;
+static const int kEntryPointFunctionIdInIdx = 1;
+
+} // anonymous namespace
+
+namespace spvtools {
+namespace opt {
+
+void InstrumentPass::MovePreludeCode(
+ BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr,
+ std::unique_ptr<BasicBlock>* new_blk_ptr) {
+ same_block_pre_.clear();
+ same_block_post_.clear();
+ // Initialize new block. Reuse label from original block.
+ new_blk_ptr->reset(new BasicBlock(std::move(ref_block_itr->GetLabel())));
+ // Move contents of original ref block up to ref instruction.
+ for (auto cii = ref_block_itr->begin(); cii != ref_inst_itr;
+ cii = ref_block_itr->begin()) {
+ Instruction* inst = &*cii;
+ inst->RemoveFromList();
+ std::unique_ptr<Instruction> mv_ptr(inst);
+ // Remember same-block ops for possible regeneration.
+ if (IsSameBlockOp(&*mv_ptr)) {
+ auto* sb_inst_ptr = mv_ptr.get();
+ same_block_pre_[mv_ptr->result_id()] = sb_inst_ptr;
+ }
+ (*new_blk_ptr)->AddInstruction(std::move(mv_ptr));
+ }
+}
+
+void InstrumentPass::MovePostludeCode(
+ UptrVectorIterator<BasicBlock> ref_block_itr,
+ std::unique_ptr<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();
+ cii = ref_block_itr->begin()) {
+ Instruction* inst = &*cii;
+ inst->RemoveFromList();
+ std::unique_ptr<Instruction> mv_inst(inst);
+ // Regenerate any same-block instruction that has not been seen in the
+ // current block.
+ if (same_block_pre_.size() > 0) {
+ CloneSameBlockOps(&mv_inst, &same_block_post_, &same_block_pre_,
+ new_blk_ptr);
+ // Remember same-block ops in this block.
+ if (IsSameBlockOp(&*mv_inst)) {
+ const uint32_t rid = mv_inst->result_id();
+ same_block_post_[rid] = rid;
+ }
+ }
+ (*new_blk_ptr)->AddInstruction(std::move(mv_inst));
+ }
+}
+
+std::unique_ptr<Instruction> InstrumentPass::NewLabel(uint32_t label_id) {
+ std::unique_ptr<Instruction> newLabel(
+ new Instruction(context(), SpvOpLabel, 0, label_id, {}));
+ get_def_use_mgr()->AnalyzeInstDefUse(&*newLabel);
+ return newLabel;
+}
+
+uint32_t InstrumentPass::GenUintCastCode(uint32_t val_id,
+ InstructionBuilder* builder) {
+ // Cast value to 32-bit unsigned if necessary
+ if (get_def_use_mgr()->GetDef(val_id)->type_id() == GetUintId())
+ return val_id;
+ return builder->AddUnaryOp(GetUintId(), SpvOpBitcast, val_id)->result_id();
+}
+
+void InstrumentPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
+ uint32_t field_offset,
+ uint32_t field_value_id,
+ InstructionBuilder* builder) {
+ // Cast value to 32-bit unsigned if necessary
+ uint32_t val_id = GenUintCastCode(field_value_id, builder);
+ // Store value
+ Instruction* data_idx_inst =
+ builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
+ builder->GetUintConstantId(field_offset));
+ uint32_t buf_id = GetOutputBufferId();
+ uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+ Instruction* achain_inst =
+ builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
+ builder->GetUintConstantId(kDebugOutputDataOffset),
+ data_idx_inst->result_id());
+ (void)builder->AddBinaryOp(0, SpvOpStore, achain_inst->result_id(), val_id);
+}
+
+void InstrumentPass::GenCommonStreamWriteCode(uint32_t record_sz,
+ uint32_t inst_id,
+ uint32_t stage_idx,
+ uint32_t base_offset_id,
+ InstructionBuilder* builder) {
+ // Store record size
+ GenDebugOutputFieldCode(base_offset_id, kInstCommonOutSize,
+ builder->GetUintConstantId(record_sz), builder);
+ // Store Shader Id
+ GenDebugOutputFieldCode(base_offset_id, kInstCommonOutShaderId,
+ builder->GetUintConstantId(shader_id_), builder);
+ // Store Instruction Idx
+ GenDebugOutputFieldCode(base_offset_id, kInstCommonOutInstructionIdx, inst_id,
+ builder);
+ // Store Stage Idx
+ GenDebugOutputFieldCode(base_offset_id, kInstCommonOutStageIdx,
+ builder->GetUintConstantId(stage_idx), builder);
+}
+
+void InstrumentPass::GenFragCoordEltDebugOutputCode(
+ uint32_t base_offset_id, uint32_t uint_frag_coord_id, uint32_t element,
+ InstructionBuilder* builder) {
+ Instruction* element_val_inst = builder->AddIdLiteralOp(
+ GetUintId(), SpvOpCompositeExtract, uint_frag_coord_id, element);
+ GenDebugOutputFieldCode(base_offset_id, kInstFragOutFragCoordX + element,
+ element_val_inst->result_id(), builder);
+}
+
+void InstrumentPass::GenBuiltinOutputCode(uint32_t builtin_id,
+ uint32_t builtin_off,
+ uint32_t base_offset_id,
+ InstructionBuilder* builder) {
+ // Load and store builtin
+ Instruction* load_inst =
+ builder->AddUnaryOp(GetUintId(), SpvOpLoad, builtin_id);
+ GenDebugOutputFieldCode(base_offset_id, builtin_off, load_inst->result_id(),
+ builder);
+}
+
+void InstrumentPass::GenUintNullOutputCode(uint32_t field_off,
+ uint32_t base_offset_id,
+ InstructionBuilder* builder) {
+ GenDebugOutputFieldCode(base_offset_id, field_off,
+ builder->GetNullId(GetUintId()), builder);
+}
+
+void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx,
+ uint32_t base_offset_id,
+ InstructionBuilder* builder) {
+ // TODO(greg-lunarg): Add support for all stages
+ switch (stage_idx) {
+ case SpvExecutionModelVertex: {
+ // Load and store VertexId and InstanceId
+ GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexId),
+ kInstVertOutVertexId, base_offset_id, builder);
+ GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceId),
+ kInstVertOutInstanceId, base_offset_id, builder);
+ } break;
+ case SpvExecutionModelGLCompute: {
+ // Load and store GlobalInvocationId. Second word is unused; store zero.
+ GenBuiltinOutputCode(
+ context()->GetBuiltinVarId(SpvBuiltInGlobalInvocationId),
+ kInstCompOutGlobalInvocationId, base_offset_id, builder);
+ GenUintNullOutputCode(kInstCompOutUnused, base_offset_id, builder);
+ } break;
+ case SpvExecutionModelGeometry: {
+ // Load and store PrimitiveId and InvocationId.
+ GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInPrimitiveId),
+ kInstGeomOutPrimitiveId, base_offset_id, builder);
+ GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInvocationId),
+ kInstGeomOutInvocationId, base_offset_id, builder);
+ } break;
+ case SpvExecutionModelTessellationControl:
+ case SpvExecutionModelTessellationEvaluation: {
+ // Load and store InvocationId. Second word is unused; store zero.
+ GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInvocationId),
+ kInstTessOutInvocationId, base_offset_id, builder);
+ GenUintNullOutputCode(kInstTessOutUnused, base_offset_id, builder);
+ } break;
+ case SpvExecutionModelFragment: {
+ // Load FragCoord and convert to Uint
+ Instruction* frag_coord_inst =
+ builder->AddUnaryOp(GetVec4FloatId(), SpvOpLoad,
+ context()->GetBuiltinVarId(SpvBuiltInFragCoord));
+ Instruction* uint_frag_coord_inst = builder->AddUnaryOp(
+ GetVec4UintId(), SpvOpBitcast, frag_coord_inst->result_id());
+ for (uint32_t u = 0; u < 2u; ++u)
+ GenFragCoordEltDebugOutputCode(
+ base_offset_id, uint_frag_coord_inst->result_id(), u, builder);
+ } break;
+ default: { assert(false && "unsupported stage"); } break;
+ }
+}
+
+void InstrumentPass::GenDebugStreamWrite(
+ uint32_t instruction_idx, uint32_t stage_idx,
+ const std::vector<uint32_t>& validation_ids, InstructionBuilder* builder) {
+ // Call debug output function. Pass func_idx, instruction_idx and
+ // validation ids as args.
+ uint32_t val_id_cnt = static_cast<uint32_t>(validation_ids.size());
+ uint32_t output_func_id = GetStreamWriteFunctionId(stage_idx, val_id_cnt);
+ std::vector<uint32_t> args = {output_func_id,
+ builder->GetUintConstantId(instruction_idx)};
+ (void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
+ (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
+}
+
+bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
+ return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
+}
+
+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) {
+ (*inst)->ForEachInId(
+ [&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) {
+ const auto map_itr = (*same_blk_post).find(*iid);
+ if (map_itr == (*same_blk_post).end()) {
+ const auto map_itr2 = (*same_blk_pre).find(*iid);
+ if (map_itr2 != (*same_blk_pre).end()) {
+ // Clone pre-call same-block ops, map result id.
+ const Instruction* in_inst = map_itr2->second;
+ std::unique_ptr<Instruction> sb_inst(in_inst->Clone(context()));
+ CloneSameBlockOps(&sb_inst, same_blk_post, same_blk_pre, block_ptr);
+ const uint32_t rid = sb_inst->result_id();
+ const uint32_t nid = this->TakeNextId();
+ get_decoration_mgr()->CloneDecorations(rid, nid);
+ sb_inst->SetResultId(nid);
+ (*same_blk_post)[rid] = nid;
+ *iid = nid;
+ (*block_ptr)->AddInstruction(std::move(sb_inst));
+ }
+ } else {
+ // Reset same-block op operand.
+ *iid = map_itr->second;
+ }
+ });
+}
+
+void InstrumentPass::UpdateSucceedingPhis(
+ std::vector<std::unique_ptr<BasicBlock>>& new_blocks) {
+ const auto first_blk = new_blocks.begin();
+ const auto last_blk = new_blocks.end() - 1;
+ const uint32_t first_id = (*first_blk)->id();
+ const uint32_t last_id = (*last_blk)->id();
+ const BasicBlock& const_last_block = *last_blk->get();
+ const_last_block.ForEachSuccessorLabel(
+ [&first_id, &last_id, this](const uint32_t succ) {
+ BasicBlock* sbp = this->id2block_[succ];
+ sbp->ForEachPhiInst([&first_id, &last_id, this](Instruction* phi) {
+ bool changed = false;
+ phi->ForEachInId([&first_id, &last_id, &changed](uint32_t* id) {
+ if (*id == first_id) {
+ *id = last_id;
+ changed = true;
+ }
+ });
+ if (changed) get_def_use_mgr()->AnalyzeInstUse(phi);
+ });
+ });
+}
+
+// Return id for output buffer uint ptr type
+uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
+ if (output_buffer_uint_ptr_id_ == 0) {
+ output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
+ GetUintId(), SpvStorageClassStorageBuffer);
+ }
+ return output_buffer_uint_ptr_id_;
+}
+
+uint32_t InstrumentPass::GetOutputBufferBinding() {
+ switch (validation_id_) {
+ case kInstValidationIdBindless:
+ return kDebugOutputBindingStream;
+ default:
+ assert(false && "unexpected validation id");
+ }
+ return 0;
+}
+
+// Return id for output buffer
+uint32_t InstrumentPass::GetOutputBufferId() {
+ if (output_buffer_id_ == 0) {
+ // If not created yet, create one
+ analysis::DecorationManager* deco_mgr = get_decoration_mgr();
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Integer uint_ty(32, false);
+ analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+ analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
+ analysis::Type* reg_uint_rarr_ty =
+ type_mgr->GetRegisteredType(&uint_rarr_ty);
+ analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
+ analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
+ uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
+ deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
+ deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
+ SpvDecorationOffset, 0);
+ deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputDataOffset,
+ SpvDecorationOffset, 4);
+ uint32_t obufTyPtrId_ =
+ type_mgr->FindPointerToType(obufTyId, SpvStorageClassStorageBuffer);
+ output_buffer_id_ = TakeNextId();
+ std::unique_ptr<Instruction> newVarOp(new Instruction(
+ context(), SpvOpVariable, obufTyPtrId_, output_buffer_id_,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+ {SpvStorageClassStorageBuffer}}}));
+ context()->AddGlobalValue(std::move(newVarOp));
+ deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationDescriptorSet,
+ desc_set_);
+ deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
+ GetOutputBufferBinding());
+ // Look for storage buffer extension. If none, create one.
+ if (!get_feature_mgr()->HasExtension(
+ kSPV_KHR_storage_buffer_storage_class)) {
+ const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
+ const auto num_chars = ext_name.size();
+ // Compute num words, accommodate the terminating null character.
+ const auto num_words = (num_chars + 1 + 3) / 4;
+ std::vector<uint32_t> ext_words(num_words, 0u);
+ std::memcpy(ext_words.data(), ext_name.data(), num_chars);
+ context()->AddExtension(std::unique_ptr<Instruction>(
+ new Instruction(context(), SpvOpExtension, 0u, 0u,
+ {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
+ }
+ }
+ return output_buffer_id_;
+}
+
+uint32_t InstrumentPass::GetVec4FloatId() {
+ if (v4float_id_ == 0) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Float float_ty(32);
+ analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
+ analysis::Vector v4float_ty(reg_float_ty, 4);
+ analysis::Type* reg_v4float_ty = type_mgr->GetRegisteredType(&v4float_ty);
+ v4float_id_ = type_mgr->GetTypeInstruction(reg_v4float_ty);
+ }
+ return v4float_id_;
+}
+
+uint32_t InstrumentPass::GetUintId() {
+ if (uint_id_ == 0) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Integer uint_ty(32, false);
+ analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+ uint_id_ = type_mgr->GetTypeInstruction(reg_uint_ty);
+ }
+ return uint_id_;
+}
+
+uint32_t InstrumentPass::GetVec4UintId() {
+ if (v4uint_id_ == 0) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Integer uint_ty(32, false);
+ analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+ analysis::Vector v4uint_ty(reg_uint_ty, 4);
+ analysis::Type* reg_v4uint_ty = type_mgr->GetRegisteredType(&v4uint_ty);
+ v4uint_id_ = type_mgr->GetTypeInstruction(reg_v4uint_ty);
+ }
+ return v4uint_id_;
+}
+
+uint32_t InstrumentPass::GetBoolId() {
+ if (bool_id_ == 0) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Bool bool_ty;
+ analysis::Type* reg_bool_ty = type_mgr->GetRegisteredType(&bool_ty);
+ bool_id_ = type_mgr->GetTypeInstruction(reg_bool_ty);
+ }
+ return bool_id_;
+}
+
+uint32_t InstrumentPass::GetVoidId() {
+ if (void_id_ == 0) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Void void_ty;
+ analysis::Type* reg_void_ty = type_mgr->GetRegisteredType(&void_ty);
+ void_id_ = type_mgr->GetTypeInstruction(reg_void_ty);
+ }
+ return void_id_;
+}
+
+uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx,
+ uint32_t val_spec_param_cnt) {
+ // Total param count is common params plus validation-specific
+ // params
+ uint32_t param_cnt = kInstCommonParamCnt + val_spec_param_cnt;
+ if (output_func_id_ == 0) {
+ // Create function
+ output_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(GetVoidId()), param_types);
+ analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
+ std::unique_ptr<Instruction> func_inst(new Instruction(
+ get_module()->context(), SpvOpFunction, GetVoidId(), output_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> output_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);
+ output_func->AddParameter(std::move(param_inst));
+ }
+ // Create first block
+ uint32_t test_blk_id = TakeNextId();
+ std::unique_ptr<Instruction> test_label(NewLabel(test_blk_id));
+ std::unique_ptr<BasicBlock> new_blk_ptr =
+ MakeUnique<BasicBlock>(std::move(test_label));
+ InstructionBuilder builder(
+ context(), &*new_blk_ptr,
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ // Gen test if debug output buffer size will not be exceeded.
+ uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
+ uint32_t buf_id = GetOutputBufferId();
+ uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+ Instruction* obuf_curr_sz_ac_inst =
+ builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
+ builder.GetUintConstantId(kDebugOutputSizeOffset));
+ // Fetch the current debug buffer written size atomically, adding the
+ // size of the record to be written.
+ uint32_t obuf_record_sz_id = builder.GetUintConstantId(obuf_record_sz);
+ uint32_t mask_none_id = builder.GetUintConstantId(SpvMemoryAccessMaskNone);
+ uint32_t scope_invok_id = builder.GetUintConstantId(SpvScopeInvocation);
+ Instruction* obuf_curr_sz_inst = builder.AddQuadOp(
+ GetUintId(), SpvOpAtomicIAdd, obuf_curr_sz_ac_inst->result_id(),
+ scope_invok_id, mask_none_id, obuf_record_sz_id);
+ uint32_t obuf_curr_sz_id = obuf_curr_sz_inst->result_id();
+ // Compute new written size
+ Instruction* obuf_new_sz_inst =
+ builder.AddBinaryOp(GetUintId(), SpvOpIAdd, obuf_curr_sz_id,
+ builder.GetUintConstantId(obuf_record_sz));
+ // Fetch the data bound
+ Instruction* obuf_bnd_inst =
+ builder.AddIdLiteralOp(GetUintId(), SpvOpArrayLength,
+ GetOutputBufferId(), kDebugOutputDataOffset);
+ // Test that new written size is less than or equal to debug output
+ // data bound
+ Instruction* obuf_safe_inst = builder.AddBinaryOp(
+ GetBoolId(), SpvOpULessThanEqual, obuf_new_sz_inst->result_id(),
+ obuf_bnd_inst->result_id());
+ uint32_t merge_blk_id = TakeNextId();
+ uint32_t write_blk_id = TakeNextId();
+ std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
+ std::unique_ptr<Instruction> write_label(NewLabel(write_blk_id));
+ (void)builder.AddConditionalBranch(obuf_safe_inst->result_id(),
+ write_blk_id, merge_blk_id, merge_blk_id,
+ SpvSelectionControlMaskNone);
+ // Close safety test block and gen write block
+ new_blk_ptr->SetParent(&*output_func);
+ output_func->AddBasicBlock(std::move(new_blk_ptr));
+ new_blk_ptr = MakeUnique<BasicBlock>(std::move(write_label));
+ builder.SetInsertPoint(&*new_blk_ptr);
+ // Generate common and stage-specific debug record members
+ GenCommonStreamWriteCode(obuf_record_sz, param_vec[kInstCommonParamInstIdx],
+ stage_idx, obuf_curr_sz_id, &builder);
+ GenStageStreamWriteCode(stage_idx, obuf_curr_sz_id, &builder);
+ // Gen writes of validation specific data
+ for (uint32_t i = 0; i < val_spec_param_cnt; ++i) {
+ GenDebugOutputFieldCode(obuf_curr_sz_id, kInstStageOutCnt + i,
+ param_vec[kInstCommonParamCnt + i], &builder);
+ }
+ // Close write block and gen merge block
+ (void)builder.AddBranch(merge_blk_id);
+ new_blk_ptr->SetParent(&*output_func);
+ output_func->AddBasicBlock(std::move(new_blk_ptr));
+ new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
+ builder.SetInsertPoint(&*new_blk_ptr);
+ // Close merge block and function and add function to module
+ (void)builder.AddNullaryOp(0, SpvOpReturn);
+ new_blk_ptr->SetParent(&*output_func);
+ output_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);
+ output_func->SetFunctionEnd(std::move(func_end_inst));
+ context()->AddFunction(std::move(output_func));
+ output_func_param_cnt_ = param_cnt;
+ }
+ assert(param_cnt == output_func_param_cnt_ && "bad arg count");
+ return output_func_id_;
+}
+
+bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
+ InstProcessFunction& pfn) {
+ bool modified = false;
+ // Compute function index
+ uint32_t function_idx = 0;
+ for (auto fii = get_module()->begin(); fii != get_module()->end(); ++fii) {
+ if (&*fii == func) break;
+ ++function_idx;
+ }
+ std::vector<std::unique_ptr<BasicBlock>> new_blks;
+ // Start count after function instruction
+ uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
+ // 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());
+ // Generate instrumentation if warranted
+ pfn(ii, bi, instruction_idx, stage_idx, &new_blks);
+ if (new_blks.size() == 0) {
+ ++ii;
+ continue;
+ }
+ // 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();
+ assert(newBlocksSize > 1);
+ UpdateSucceedingPhis(new_blks);
+ // Replace original block with new block(s)
+ bi = bi.Erase();
+ for (auto& bb : new_blks) {
+ bb->SetParent(func);
+ }
+ bi = bi.InsertBefore(&new_blks);
+ // Reset block iterator to last new block
+ for (size_t i = 0; i < newBlocksSize - 1; i++) ++bi;
+ modified = true;
+ // Restart instrumenting at beginning of last new block,
+ // but skip over any new phi or copy instruction.
+ ii = bi->begin();
+ if (ii->opcode() == SpvOpPhi || ii->opcode() == SpvOpCopyObject) ++ii;
+ new_blks.clear();
+ }
+ }
+ return modified;
+}
+
+bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
+ std::queue<uint32_t>* roots,
+ uint32_t stage_idx) {
+ bool modified = false;
+ std::unordered_set<uint32_t> done;
+ // Process all functions from roots
+ while (!roots->empty()) {
+ const uint32_t fi = roots->front();
+ roots->pop();
+ if (done.insert(fi).second) {
+ Function* fn = id2function_.at(fi);
+ // Add calls first so we don't add new output function
+ AddCalls(fn, roots);
+ modified = InstrumentFunction(fn, stage_idx, pfn) || modified;
+ }
+ }
+ return modified;
+}
+
+bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
+ // Make sure all entry points have the same execution model. Do not
+ // instrument if they do not.
+ // TODO(greg-lunarg): Handle mixed stages. Technically, a shader module
+ // can contain entry points with different execution models, although
+ // such modules will likely be rare as GLSL and HLSL are geared toward
+ // one model per module. In such cases we will need
+ // to clone any functions which are in the call trees of entrypoints
+ // with differing execution models.
+ uint32_t ecnt = 0;
+ uint32_t stage = SpvExecutionModelMax;
+ for (auto& e : get_module()->entry_points()) {
+ if (ecnt == 0)
+ stage = e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx);
+ else if (e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx) != stage)
+ return false;
+ ++ecnt;
+ }
+ // Only supporting vertex, fragment and compute shaders at the moment.
+ // TODO(greg-lunarg): Handle all stages.
+ if (stage != SpvExecutionModelVertex && stage != SpvExecutionModelFragment &&
+ stage != SpvExecutionModelGeometry &&
+ stage != SpvExecutionModelGLCompute &&
+ stage != SpvExecutionModelTessellationControl &&
+ stage != SpvExecutionModelTessellationEvaluation)
+ return false;
+ // Add together the roots of all entry points
+ std::queue<uint32_t> roots;
+ for (auto& e : get_module()->entry_points()) {
+ roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+ }
+ bool modified = InstProcessCallTreeFromRoots(pfn, &roots, stage);
+ return modified;
+}
+
+void InstrumentPass::InitializeInstrument() {
+ output_buffer_id_ = 0;
+ output_buffer_uint_ptr_id_ = 0;
+ output_func_id_ = 0;
+ output_func_param_cnt_ = 0;
+ v4float_id_ = 0;
+ uint_id_ = 0;
+ v4uint_id_ = 0;
+ bool_id_ = 0;
+ void_id_ = 0;
+
+ // clear collections
+ id2function_.clear();
+ id2block_.clear();
+
+ // Initialize function and block maps.
+ for (auto& fn : *get_module()) {
+ id2function_[fn.result_id()] = &fn;
+ for (auto& blk : fn) {
+ id2block_[blk.id()] = &blk;
+ }
+ }
+
+ // Calculate instruction offset of first function
+ uint32_t pre_func_size = 0;
+ Module* module = get_module();
+ for (auto& i : context()->capabilities()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ for (auto& i : module->extensions()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ for (auto& i : module->ext_inst_imports()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ ++pre_func_size; // memory_model
+ for (auto& i : module->entry_points()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ for (auto& i : module->execution_modes()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ for (auto& i : module->debugs1()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ for (auto& i : module->debugs2()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ for (auto& i : module->debugs3()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ for (auto& i : module->annotations()) {
+ (void)i;
+ ++pre_func_size;
+ }
+ 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 and end instructions
+ uint32_t func_size = 2;
+ for (auto& blk : *prev_fn) {
+ // Count label
+ func_size += 1;
+ for (auto& inst : blk) {
+ func_size += 1;
+ func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
+ }
+ }
+ funcIdx2offset_[func_idx] = func_size;
+ ++prev_fn;
+ ++func_idx;
+ }
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
new file mode 100644
index 0000000..476a894
--- /dev/null
+++ b/source/opt/instrument_pass.h
@@ -0,0 +1,356 @@
+// Copyright (c) 2018 The Khronos Group Inc.
+// Copyright (c) 2018 Valve Corporation
+// Copyright (c) 2018 LunarG Inc.
+//
+// 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 LIBSPIRV_OPT_INSTRUMENT_PASS_H_
+#define LIBSPIRV_OPT_INSTRUMENT_PASS_H_
+
+#include <list>
+#include <memory>
+#include <vector>
+
+#include "source/opt/ir_builder.h"
+#include "source/opt/pass.h"
+#include "spirv-tools/instrument.hpp"
+
+// This is a base class to assist in the creation of passes which instrument
+// shader modules. More specifically, passes which replace instructions with a
+// larger and more capable set of instructions. Commonly, these new
+// instructions will add testing of operands and execute different
+// instructions depending on the outcome, including outputting of debug
+// information into a buffer created especially for that purpose.
+//
+// This class contains helper functions to create an InstProcessFunction,
+// which is the heart of any derived class implementing a specific
+// instrumentation pass. It takes an instruction as an argument, decides
+// if it should be instrumented, and generates code to replace it. This class
+// also supplies function InstProcessEntryPointCallTree which applies the
+// InstProcessFunction to every reachable instruction in a module and replaces
+// the instruction with new instructions if generated.
+//
+// Chief among the helper functions are output code generation functions,
+// used to generate code in the shader which writes data to output buffers
+// associated with that validation. Currently one such function,
+// GenDebugStreamWrite, exists. Other such functions may be added in the
+// future. Each is accompanied by documentation describing the format of
+// its output buffer.
+//
+// A validation pass may read or write multiple buffers. All such buffers
+// are located in a single debug descriptor set whose index is passed at the
+// creation of the instrumentation pass. The bindings of the buffers used by
+// a validation pass are permanantly assigned and fixed and documented by
+// the kDebugOutput* static consts.
+
+namespace spvtools {
+namespace opt {
+
+// Validation Ids
+// These are used to identify the general validation being done and map to
+// its output buffers.
+static const uint32_t kInstValidationIdBindless = 0;
+
+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>>*)>;
+
+ virtual ~InstrumentPass() = default;
+
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId;
+ }
+
+ protected:
+ // Create instrumentation pass which utilizes descriptor set |desc_set|
+ // for debug input and output buffers and writes |shader_id| into debug
+ // output records.
+ InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id)
+ : Pass(),
+ desc_set_(desc_set),
+ shader_id_(shader_id),
+ validation_id_(validation_id) {}
+
+ // Initialize state for instrumentation of module by |validation_id|.
+ void InitializeInstrument();
+
+ // Call |pfn| on all instructions in all functions in the call tree of the
+ // entry points in |module|. If code is generated for an instruction, replace
+ // the instruction's block with the new blocks that are generated. Continue
+ // processing at the top of the last new block.
+ bool InstProcessEntryPointCallTree(InstProcessFunction& pfn);
+
+ // Move all code in |ref_block_itr| preceding the instruction |ref_inst_itr|
+ // to be instrumented into block |new_blk_ptr|.
+ void MovePreludeCode(BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr,
+ std::unique_ptr<BasicBlock>* new_blk_ptr);
+
+ // 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);
+
+ // Generate instructions in |builder| which will atomically fetch and
+ // increment the size of the debug output buffer stream of the current
+ // validation and write a record to the end of the stream, if enough space
+ // in the buffer remains. The record will contain the index of the function
+ // and instruction within that function |func_idx, instruction_idx| which
+ // generated the record. It will also contain additional information to
+ // identify the instance of the shader, depending on the stage |stage_idx|
+ // of the shader. Finally, the record will contain validation-specific
+ // data contained in |validation_ids| which will identify the validation
+ // error as well as the values involved in the error.
+ //
+ // The output buffer binding written to by the code generated by the function
+ // is determined by the validation id specified when each specific
+ // instrumentation pass is created.
+ //
+ // The output buffer is a sequence of 32-bit values with the following
+ // format (where all elements are unsigned 32-bit unless otherwise noted):
+ //
+ // Size
+ // Record0
+ // Record1
+ // Record2
+ // ...
+ //
+ // Size is the number of 32-bit values that have been written or
+ // attempted to be written to the output buffer, excluding the Size. It is
+ // initialized to 0. If the size of attempts to write the buffer exceeds
+ // the actual size of the buffer, it is possible that this field can exceed
+ // the actual size of the buffer.
+ //
+ // Each Record* is a variable-length sequence of 32-bit values with the
+ // following format defined using static const offsets in the .cpp file:
+ //
+ // Record Size
+ // Shader ID
+ // Instruction Index
+ // Stage
+ // Stage-specific Word 0
+ // Stage-specific Word 1
+ // Validation Error Code
+ // Validation-specific Word 0
+ // Validation-specific Word 1
+ // Validation-specific Word 2
+ // ...
+ //
+ // Each record consists of three subsections: members common across all
+ // validation, members specific to the stage, and members specific to a
+ // validation.
+ //
+ // The Record Size is the number of 32-bit words in the record, including
+ // the Record Size word.
+ //
+ // Shader ID is a value that identifies which shader has generated the
+ // validation error. It is passed when the instrumentation pass is created.
+ //
+ // The Instruction Index is the position of the instruction within the
+ // SPIR-V file which is in error.
+ //
+ // The Stage is the pipeline stage which has generated the error as defined
+ // by the SpvExecutionModel_ enumeration. This is used to interpret the
+ // following Stage-specific words.
+ //
+ // The Stage-specific Words identify which invocation of the shader generated
+ // the error. Every stage will write two words, although in some cases the
+ // second word is unused and so zero is written. Vertex shaders will write
+ // the Vertex and Instance ID. Fragment shaders will write FragCoord.xy.
+ // Compute shaders will write the Global Invocation ID and zero (unused).
+ // Both tesselation shaders will write the Invocation Id and zero (unused).
+ // The geometry shader will write the Primitive ID and Invocation ID.
+ //
+ // The Validation Error Code specifies the exact error which has occurred.
+ // These are enumerated with the kInstError* static consts. This allows
+ // multiple validation layers to use the same, single output buffer.
+ //
+ // The Validation-specific Words are a validation-specific number of 32-bit
+ // words which give further information on the validation error that
+ // occurred. These are documented further in each file containing the
+ // validation-specific class which derives from this base class.
+ //
+ // Because the code that is generated checks against the size of the buffer
+ // before writing, the size of the debug out buffer can be used by the
+ // validation layer to control the number of error records that are written.
+ void GenDebugStreamWrite(uint32_t instruction_idx, uint32_t stage_idx,
+ const std::vector<uint32_t>& validation_ids,
+ InstructionBuilder* builder);
+
+ // Generate code to cast |value_id| to unsigned, if needed. Return
+ // an id to the unsigned equivalent.
+ uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
+
+ // Return new label.
+ std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
+
+ // Return id for 32-bit unsigned type
+ uint32_t GetUintId();
+
+ // Return id for 32-bit unsigned type
+ uint32_t GetBoolId();
+
+ // Return id for void type
+ uint32_t GetVoidId();
+
+ // Return id for output buffer uint type
+ uint32_t GetOutputBufferUintPtrId();
+
+ // Return binding for output buffer for current validation.
+ uint32_t GetOutputBufferBinding();
+
+ // Return id for debug output buffer
+ uint32_t GetOutputBufferId();
+
+ // Return id for v4float type
+ uint32_t GetVec4FloatId();
+
+ // Return id for v4uint type
+ uint32_t GetVec4UintId();
+
+ // Return id for output function. Define if it doesn't exist with
+ // |val_spec_arg_cnt| validation-specific uint32 arguments.
+ uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
+ uint32_t val_spec_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
+ // top of the last new block.
+ bool InstrumentFunction(Function* func, uint32_t stage_idx,
+ InstProcessFunction& pfn);
+
+ // Call |pfn| on all functions in the call tree of the function
+ // ids in |roots|.
+ bool InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
+ std::queue<uint32_t>* roots,
+ uint32_t stage_idx);
+
+ // Gen code into |builder| to write |field_value_id| into debug output
+ // buffer at |base_offset_id| + |field_offset|.
+ void GenDebugOutputFieldCode(uint32_t base_offset_id, uint32_t field_offset,
+ uint32_t field_value_id,
+ InstructionBuilder* builder);
+
+ // Generate instructions into |builder| which will write the members
+ // of the debug output record common for all stages and validations at
+ // |base_off|.
+ void GenCommonStreamWriteCode(uint32_t record_sz, uint32_t instruction_idx,
+ uint32_t stage_idx, uint32_t base_off,
+ InstructionBuilder* builder);
+
+ // Generate instructions into |builder| which will write
+ // |uint_frag_coord_id| at |component| of the record at |base_offset_id| of
+ // the debug output buffer .
+ void GenFragCoordEltDebugOutputCode(uint32_t base_offset_id,
+ uint32_t uint_frag_coord_id,
+ uint32_t component,
+ InstructionBuilder* builder);
+
+ // Generate instructions into |builder| which will load the uint |builtin_id|
+ // and write it into the debug output buffer at |base_off| + |builtin_off|.
+ void GenBuiltinOutputCode(uint32_t builtin_id, uint32_t builtin_off,
+ uint32_t base_off, InstructionBuilder* builder);
+
+ // Generate instructions into |builder| which will write a uint null into
+ // the debug output buffer at |base_off| + |builtin_off|.
+ void GenUintNullOutputCode(uint32_t field_off, uint32_t base_off,
+ InstructionBuilder* builder);
+
+ // Generate instructions into |builder| which will write the |stage_idx|-
+ // specific members of the debug output stream at |base_off|.
+ void GenStageStreamWriteCode(uint32_t stage_idx, uint32_t base_off,
+ InstructionBuilder* builder);
+
+ // Return true if instruction must be in the same block that its result
+ // is used.
+ bool IsSameBlockOp(const Instruction* inst) const;
+
+ // Clone operands which must be in same block as consumer instructions.
+ // Look in same_blk_pre for instructions that need cloning. Look in
+ // same_blk_post for instructions already cloned. Add cloned instruction
+ // to same_blk_post.
+ void 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);
+
+ // Update phis in succeeding blocks to point to new last block
+ void UpdateSucceedingPhis(
+ std::vector<std::unique_ptr<BasicBlock>>& new_blocks);
+
+ // Debug descriptor set index
+ uint32_t desc_set_;
+
+ // Shader module ID written into output record
+ uint32_t shader_id_;
+
+ // Map from function id to function pointer.
+ std::unordered_map<uint32_t, Function*> id2function_;
+
+ // Map from block's label id to block. TODO(dnovillo): This is superfluous wrt
+ // 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_;
+
+ // result id for OpConstantFalse
+ uint32_t validation_id_;
+
+ // id for output buffer variable
+ uint32_t output_buffer_id_;
+
+ // type id for output buffer element
+ uint32_t output_buffer_uint_ptr_id_;
+
+ // id for debug output function
+ uint32_t output_func_id_;
+
+ // param count for output function
+ uint32_t output_func_param_cnt_;
+
+ // id for v4float type
+ uint32_t v4float_id_;
+
+ // id for v4float type
+ uint32_t v4uint_id_;
+
+ // id for 32-bit unsigned type
+ uint32_t uint_id_;
+
+ // id for bool type
+ uint32_t bool_id_;
+
+ // id for void type
+ uint32_t void_id_;
+
+ // Pre-instrumentation same-block insts
+ std::unordered_map<uint32_t, Instruction*> same_block_pre_;
+
+ // Post-instrumentation same-block op ids
+ std::unordered_map<uint32_t, uint32_t> same_block_post_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // LIBSPIRV_OPT_INSTRUMENT_PASS_H_
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index a07b255..434c380 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -58,6 +58,78 @@
: InstructionBuilder(context, parent_block, parent_block->end(),
preserved_analyses) {}
+ Instruction* AddNullaryOp(uint32_t type_id, SpvOp opcode) {
+ std::unique_ptr<Instruction> newUnOp(new Instruction(
+ GetContext(), opcode, type_id,
+ opcode == SpvOpReturn ? 0 : GetContext()->TakeNextId(), {}));
+ return AddInstruction(std::move(newUnOp));
+ }
+
+ Instruction* AddUnaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1) {
+ std::unique_ptr<Instruction> newUnOp(new Instruction(
+ GetContext(), opcode, type_id, GetContext()->TakeNextId(),
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}}));
+ return AddInstruction(std::move(newUnOp));
+ }
+
+ Instruction* AddBinaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
+ uint32_t operand2) {
+ std::unique_ptr<Instruction> newBinOp(new Instruction(
+ GetContext(), opcode, type_id,
+ opcode == SpvOpStore ? 0 : GetContext()->TakeNextId(),
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}}}));
+ return AddInstruction(std::move(newBinOp));
+ }
+
+ Instruction* AddTernaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
+ uint32_t operand2, uint32_t operand3) {
+ std::unique_ptr<Instruction> newTernOp(new Instruction(
+ GetContext(), opcode, type_id, GetContext()->TakeNextId(),
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand3}}}));
+ return AddInstruction(std::move(newTernOp));
+ }
+
+ Instruction* AddQuadOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
+ uint32_t operand2, uint32_t operand3,
+ uint32_t operand4) {
+ std::unique_ptr<Instruction> newQuadOp(new Instruction(
+ GetContext(), opcode, type_id, GetContext()->TakeNextId(),
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand3}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand4}}}));
+ return AddInstruction(std::move(newQuadOp));
+ }
+
+ Instruction* AddIdLiteralOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
+ uint32_t operand2) {
+ std::unique_ptr<Instruction> newBinOp(new Instruction(
+ GetContext(), opcode, type_id, GetContext()->TakeNextId(),
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {operand2}}}));
+ return AddInstruction(std::move(newBinOp));
+ }
+
+ // Creates an N-ary instruction of |opcode|.
+ // |typid| must be the id of the instruction's type.
+ // |operands| must be a sequence of operand ids.
+ // Use |result| for the result id if non-zero.
+ Instruction* AddNaryOp(uint32_t type_id, SpvOp opcode,
+ const std::vector<uint32_t>& operands,
+ uint32_t result = 0) {
+ std::vector<Operand> ops;
+ for (size_t i = 0; i < operands.size(); i++) {
+ ops.push_back({SPV_OPERAND_TYPE_ID, {operands[i]}});
+ }
+ std::unique_ptr<Instruction> new_inst(new Instruction(
+ GetContext(), opcode, type_id,
+ result != 0 ? result : GetContext()->TakeNextId(), ops));
+ return AddInstruction(std::move(new_inst));
+ }
+
// Creates a new selection merge instruction.
// The id |merge_id| is the merge basic block id.
Instruction* AddSelectionMerge(
@@ -167,15 +239,10 @@
// The id |type| must be the id of the phi instruction's type.
// The vector |incomings| must be a sequence of pairs of <definition id,
// parent id>.
- Instruction* AddPhi(uint32_t type, const std::vector<uint32_t>& incomings) {
+ Instruction* AddPhi(uint32_t type, const std::vector<uint32_t>& incomings,
+ uint32_t result = 0) {
assert(incomings.size() % 2 == 0 && "A sequence of pairs is expected");
- std::vector<Operand> phi_ops;
- for (size_t i = 0; i < incomings.size(); i++) {
- phi_ops.push_back({SPV_OPERAND_TYPE_ID, {incomings[i]}});
- }
- std::unique_ptr<Instruction> phi_inst(new Instruction(
- GetContext(), SpvOpPhi, type, GetContext()->TakeNextId(), phi_ops));
- return AddInstruction(std::move(phi_inst));
+ return AddNaryOp(type, SpvOpPhi, incomings, result);
}
// Creates an addition instruction.
@@ -249,8 +316,8 @@
// Adds a signed int32 constant to the binary.
// The |value| parameter is the constant value to be added.
- Instruction* Add32BitSignedIntegerConstant(int32_t value) {
- return Add32BitConstantInteger<int32_t>(value, true);
+ Instruction* GetSintConstant(int32_t value) {
+ return GetIntConstant<int32_t>(value, true);
}
// Create a composite construct.
@@ -270,8 +337,23 @@
}
// Adds an unsigned int32 constant to the binary.
// The |value| parameter is the constant value to be added.
- Instruction* Add32BitUnsignedIntegerConstant(uint32_t value) {
- return Add32BitConstantInteger<uint32_t>(value, false);
+ Instruction* GetUintConstant(uint32_t value) {
+ return GetIntConstant<uint32_t>(value, false);
+ }
+
+ uint32_t GetUintConstantId(uint32_t value) {
+ Instruction* uint_inst = GetUintConstant(value);
+ return uint_inst->result_id();
+ }
+
+ uint32_t GetNullId(uint32_t type_id) {
+ analysis::TypeManager* type_mgr = GetContext()->get_type_mgr();
+ analysis::ConstantManager* const_mgr = GetContext()->get_constant_mgr();
+ const analysis::Type* type = type_mgr->GetType(type_id);
+ const analysis::Constant* null_const = const_mgr->GetConstant(type, {});
+ Instruction* null_inst =
+ const_mgr->GetDefiningInstruction(null_const, type_id);
+ return null_inst->result_id();
}
// Adds either a signed or unsigned 32 bit integer constant to the binary
@@ -279,7 +361,7 @@
// signed constant otherwise as an unsigned constant. If |sign| is false the
// value must not be a negative number.
template <typename T>
- Instruction* Add32BitConstantInteger(T value, bool sign) {
+ Instruction* GetIntConstant(T value, bool sign) {
// Assert that we are not trying to store a negative number in an unsigned
// type.
if (!sign)
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index 4097484..038ad6d 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -21,6 +21,15 @@
#include "source/opt/mem_pass.h"
#include "source/opt/reflect.h"
+namespace {
+
+static const int kSpvDecorateTargetIdInIdx = 0;
+static const int kSpvDecorateDecorationInIdx = 1;
+static const int kSpvDecorateBuiltinInIdx = 2;
+static const int kEntryPointInterfaceInIdx = 3;
+
+} // anonymous namespace
+
namespace spvtools {
namespace opt {
@@ -43,6 +52,9 @@
if (set & kAnalysisLoopAnalysis) {
ResetLoopAnalysis();
}
+ if (set & kAnalysisBuiltinVarId) {
+ ResetBuiltinAnalysis();
+ }
if (set & kAnalysisNameMap) {
BuildIdToNameMap();
}
@@ -79,6 +91,9 @@
if (analyses_to_invalidate & kAnalysisCombinators) {
combinator_ops_.clear();
}
+ if (analyses_to_invalidate & kAnalysisBuiltinVarId) {
+ builtin_var_id_map_.clear();
+ }
if (analyses_to_invalidate & kAnalysisCFG) {
cfg_.reset(nullptr);
}
@@ -573,6 +588,91 @@
return &it->second;
}
+uint32_t IRContext::FindBuiltinVar(uint32_t builtin) {
+ for (auto& a : module_->annotations()) {
+ if (a.opcode() != SpvOpDecorate) continue;
+ if (a.GetSingleWordInOperand(kSpvDecorateDecorationInIdx) !=
+ SpvDecorationBuiltIn)
+ continue;
+ if (a.GetSingleWordInOperand(kSpvDecorateBuiltinInIdx) != builtin) continue;
+ uint32_t target_id = a.GetSingleWordInOperand(kSpvDecorateTargetIdInIdx);
+ Instruction* b_var = get_def_use_mgr()->GetDef(target_id);
+ if (b_var->opcode() != SpvOpVariable) continue;
+ return target_id;
+ }
+ return 0;
+}
+
+void IRContext::AddVarToEntryPoints(uint32_t var_id) {
+ uint32_t ocnt = 0;
+ for (auto& e : module()->entry_points()) {
+ bool found = false;
+ e.ForEachInOperand([&ocnt, &found, &var_id](const uint32_t* idp) {
+ if (ocnt >= kEntryPointInterfaceInIdx) {
+ if (*idp == var_id) found = true;
+ }
+ ++ocnt;
+ });
+ if (!found) {
+ e.AddOperand({SPV_OPERAND_TYPE_ID, {var_id}});
+ get_def_use_mgr()->AnalyzeInstDefUse(&e);
+ }
+ }
+}
+
+uint32_t IRContext::GetBuiltinVarId(uint32_t builtin) {
+ if (!AreAnalysesValid(kAnalysisBuiltinVarId)) ResetBuiltinAnalysis();
+ // If cached, return it.
+ std::unordered_map<uint32_t, uint32_t>::iterator it =
+ builtin_var_id_map_.find(builtin);
+ if (it != builtin_var_id_map_.end()) return it->second;
+ // Look for one in shader
+ uint32_t var_id = FindBuiltinVar(builtin);
+ if (var_id == 0) {
+ // If not found, create it
+ // TODO(greg-lunarg): Add support for all builtins
+ analysis::TypeManager* type_mgr = get_type_mgr();
+ analysis::Type* reg_type;
+ switch (builtin) {
+ case SpvBuiltInFragCoord: {
+ analysis::Float float_ty(32);
+ analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
+ analysis::Vector v4float_ty(reg_float_ty, 4);
+ reg_type = type_mgr->GetRegisteredType(&v4float_ty);
+ break;
+ }
+ case SpvBuiltInVertexId:
+ case SpvBuiltInInstanceId:
+ case SpvBuiltInPrimitiveId:
+ case SpvBuiltInInvocationId:
+ case SpvBuiltInGlobalInvocationId: {
+ analysis::Integer uint_ty(32, false);
+ reg_type = type_mgr->GetRegisteredType(&uint_ty);
+ break;
+ }
+ default: {
+ assert(false && "unhandled builtin");
+ return 0;
+ }
+ }
+ uint32_t type_id = type_mgr->GetTypeInstruction(reg_type);
+ uint32_t varTyPtrId =
+ type_mgr->FindPointerToType(type_id, SpvStorageClassInput);
+ var_id = TakeNextId();
+ std::unique_ptr<Instruction> newVarOp(
+ new Instruction(this, SpvOpVariable, varTyPtrId, var_id,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+ {SpvStorageClassInput}}}));
+ get_def_use_mgr()->AnalyzeInstDefUse(&*newVarOp);
+ module()->AddGlobalValue(std::move(newVarOp));
+ get_decoration_mgr()->AddDecorationVal(var_id, SpvDecorationBuiltIn,
+ builtin);
+ AddVarToEntryPoints(var_id);
+ }
+ builtin_var_id_map_[builtin] = var_id;
+ return var_id;
+}
+
// Gets the dominator analysis for function |f|.
DominatorAnalysis* IRContext::GetDominatorAnalysis(const Function* f) {
if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) {
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 742c868..6e9eda8 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -73,7 +73,8 @@
kAnalysisRegisterPressure = 1 << 9,
kAnalysisValueNumberTable = 1 << 10,
kAnalysisStructuredCFG = 1 << 11,
- kAnalysisEnd = 1 << 12
+ kAnalysisBuiltinVarId = 1 << 12,
+ kAnalysisEnd = 1 << 13
};
friend inline Analysis operator|(Analysis lhs, Analysis rhs);
@@ -472,6 +473,11 @@
uint32_t max_id_bound() const { return max_id_bound_; }
void set_max_id_bound(uint32_t new_bound) { max_id_bound_ = new_bound; }
+ // Return id of variable only decorated with |builtin|, if in module.
+ // Create variable and return its id otherwise. If builtin not currently
+ // supported, return 0.
+ uint32_t GetBuiltinVarId(uint32_t builtin);
+
private:
// Builds the def-use manager from scratch, even if it was already valid.
void BuildDefUseManager() {
@@ -543,6 +549,13 @@
valid_analyses_ = valid_analyses_ | kAnalysisLoopAnalysis;
}
+ // Removes all computed loop descriptors.
+ void ResetBuiltinAnalysis() {
+ // Clear the cache.
+ builtin_var_id_map_.clear();
+ valid_analyses_ = valid_analyses_ | kAnalysisBuiltinVarId;
+ }
+
// Analyzes the features in the owned module. Builds the manager if required.
void AnalyzeFeatures() {
feature_mgr_ = MakeUnique<FeatureManager>(grammar_);
@@ -566,6 +579,13 @@
// true if the cfg is invalidated.
bool CheckCFG();
+ // Return id of variable only decorated with |builtin|, if in module.
+ // Return 0 otherwise.
+ uint32_t FindBuiltinVar(uint32_t builtin);
+
+ // Add |var_id| to all entry points in module.
+ void AddVarToEntryPoints(uint32_t var_id);
+
// The SPIR-V syntax context containing grammar tables for opcodes and
// operands.
spv_context syntax_context_;
@@ -607,6 +627,10 @@
// without side-effect.
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> combinator_ops_;
+ // Opcodes of shader capability core executable instructions
+ // without side-effect.
+ std::unordered_map<uint32_t, uint32_t> builtin_var_id_map_;
+
// The CFG for all the functions in |module_|.
std::unique_ptr<CFG> cfg_;
@@ -786,6 +810,9 @@
}
void IRContext::AddExtension(std::unique_ptr<Instruction>&& e) {
+ if (AreAnalysesValid(kAnalysisDefUse)) {
+ get_def_use_mgr()->AnalyzeInstDefUse(e.get());
+ }
module()->AddExtension(std::move(e));
}
@@ -827,6 +854,9 @@
if (AreAnalysesValid(kAnalysisDecorations)) {
get_decoration_mgr()->AddDecoration(a.get());
}
+ if (AreAnalysesValid(kAnalysisDefUse)) {
+ get_def_use_mgr()->AnalyzeInstDefUse(a.get());
+ }
module()->AddAnnotationInst(std::move(a));
}
@@ -838,10 +868,10 @@
}
void IRContext::AddGlobalValue(std::unique_ptr<Instruction>&& v) {
- module()->AddGlobalValue(std::move(v));
if (AreAnalysesValid(kAnalysisDefUse)) {
- get_def_use_mgr()->AnalyzeInstDef(&*(--types_values_end()));
+ get_def_use_mgr()->AnalyzeInstDefUse(&*v);
}
+ module()->AddGlobalValue(std::move(v));
}
void IRContext::AddFunction(std::unique_ptr<Function>&& f) {
diff --git a/source/opt/loop_peeling.cpp b/source/opt/loop_peeling.cpp
index 7d27480..227ba4a 100644
--- a/source/opt/loop_peeling.cpp
+++ b/source/opt/loop_peeling.cpp
@@ -151,7 +151,7 @@
context_, &*insert_point,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
Instruction* uint_1_cst =
- builder.Add32BitConstantInteger<uint32_t>(1, int_type_->IsSigned());
+ builder.GetIntConstant<uint32_t>(1, int_type_->IsSigned());
// Create the increment.
// Note that we do "1 + 1" here, one of the operand should the phi
// value but we don't have it yet. The operand will be set latter.
@@ -162,8 +162,7 @@
canonical_induction_variable_ = builder.AddPhi(
uint_1_cst->type_id(),
- {builder.Add32BitConstantInteger<uint32_t>(0, int_type_->IsSigned())
- ->result_id(),
+ {builder.GetIntConstant<uint32_t>(0, int_type_->IsSigned())->result_id(),
GetClonedLoop()->GetPreHeaderBlock()->id(), iv_inc->result_id(),
GetClonedLoop()->GetLatchBlock()->id()});
// Connect everything.
@@ -422,7 +421,7 @@
context_, &*cloned_loop_->GetPreHeaderBlock()->tail(),
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
Instruction* factor =
- builder.Add32BitConstantInteger(peel_factor, int_type_->IsSigned());
+ builder.GetIntConstant(peel_factor, int_type_->IsSigned());
Instruction* has_remaining_iteration = builder.AddLessThan(
factor->result_id(), loop_iteration_count_->result_id());
@@ -484,7 +483,7 @@
context_, &*cloned_loop_->GetPreHeaderBlock()->tail(),
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
Instruction* factor =
- builder.Add32BitConstantInteger(peel_factor, int_type_->IsSigned());
+ builder.GetIntConstant(peel_factor, int_type_->IsSigned());
Instruction* has_remaining_iteration = builder.AddLessThan(
factor->result_id(), loop_iteration_count_->result_id());
@@ -677,8 +676,8 @@
InstructionBuilder(
context(), loop->GetHeaderBlock(),
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping)
- .Add32BitConstantInteger<uint32_t>(static_cast<uint32_t>(iterations),
- is_signed),
+ .GetIntConstant<uint32_t>(static_cast<uint32_t>(iterations),
+ is_signed),
canonical_induction_variable);
if (!peeler.CanPeelLoop()) {
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index 96ee94d..d3e733a 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -452,11 +452,9 @@
// If the remainder is negative then we add a signed constant, otherwise just
// add an unsigned constant.
if (remainder < 0) {
- new_constant =
- builder.Add32BitSignedIntegerConstant(static_cast<int32_t>(remainder));
+ new_constant = builder.GetSintConstant(static_cast<int32_t>(remainder));
} else {
- new_constant = builder.Add32BitUnsignedIntegerConstant(
- static_cast<int32_t>(remainder));
+ new_constant = builder.GetUintConstant(static_cast<int32_t>(remainder));
}
uint32_t constant_id = new_constant->result_id();
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 5a1b314..b1bb345 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -368,6 +368,12 @@
RegisterPass(CreateWorkaround1209Pass());
} else if (pass_name == "replace-invalid-opcode") {
RegisterPass(CreateReplaceInvalidOpcodePass());
+ } else if (pass_name == "inst-bindless-check") {
+ RegisterPass(CreateInstBindlessCheckPass(7, 23));
+ RegisterPass(CreateSimplificationPass());
+ RegisterPass(CreateDeadBranchElimPass());
+ RegisterPass(CreateBlockMergePass());
+ RegisterPass(CreateAggressiveDCEPass());
} else if (pass_name == "simplify-instructions") {
RegisterPass(CreateSimplificationPass());
} else if (pass_name == "ssa-rewrite") {
@@ -747,4 +753,11 @@
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::CombineAccessChains>());
}
+
+Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
+ uint32_t shader_id) {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
+}
+
} // namespace spvtools
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
index 4c4a232..4bf719d 100644
--- a/source/opt/pass.cpp
+++ b/source/opt/pass.cpp
@@ -44,8 +44,9 @@
// Collect all of the entry points as the roots.
std::queue<uint32_t> roots;
- for (auto& e : module->entry_points())
+ for (auto& e : module->entry_points()) {
roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+ }
return ProcessCallTreeFromRoots(pfn, id2function, &roots);
}
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 987af1e..25e3a47 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -36,6 +36,7 @@
#include "source/opt/if_conversion.h"
#include "source/opt/inline_exhaustive_pass.h"
#include "source/opt/inline_opaque_pass.h"
+#include "source/opt/inst_bindless_check_pass.h"
#include "source/opt/licm_pass.h"
#include "source/opt/local_access_chain_convert_pass.h"
#include "source/opt/local_redundancy_elimination.h"
diff --git a/source/opt/types.cpp b/source/opt/types.cpp
index 15cff54..0cbe5c8 100644
--- a/source/opt/types.cpp
+++ b/source/opt/types.cpp
@@ -555,6 +555,14 @@
}
}
+Function::Function(Type* ret_type, std::vector<const Type*>& params)
+ : Type(kFunction), return_type_(ret_type), param_types_(params) {
+ for (auto* t : params) {
+ (void)t;
+ assert(!t->AsVoid());
+ }
+}
+
bool Function::IsSameImpl(const Type* that, IsSameCache* seen) const {
const Function* ft = that->AsFunction();
if (!ft) return false;
diff --git a/source/opt/types.h b/source/opt/types.h
index 625f342..28731c6 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -491,6 +491,7 @@
class Function : public Type {
public:
Function(Type* ret_type, const std::vector<const Type*>& params);
+ Function(Type* ret_type, std::vector<const Type*>& params);
Function(const Function&) = default;
std::string str() const override;
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 240aed1..05957b2 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -42,6 +42,7 @@
inline_opaque_test.cpp
inline_test.cpp
insert_extract_elim_test.cpp
+ inst_bindless_check_test.cpp
instruction_list_test.cpp
instruction_test.cpp
ir_builder.cpp
@@ -86,4 +87,3 @@
LIBS SPIRV-Tools-opt
PCH_FILE pch_test_opt
)
-
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
new file mode 100644
index 0000000..1a1a194
--- /dev/null
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -0,0 +1,1850 @@
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+//
+// 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 <string>
+#include <vector>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using InstBindlessTest = PassTest<::testing::Test>;
+
+TEST_F(InstBindlessTest, Simple) {
+ // Texture2D g_tColor[128];
+ //
+ // layout(push_constant) cbuffer PerViewConstantBuffer_t
+ // {
+ // uint g_nDataIdx;
+ // };
+ //
+ // 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[ g_nDataIdx ].Sample(g_sAniso, i.vTextureCoords.xy);
+ // return ps_output;
+ // }
+
+ const std::string entry_before =
+ 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
+)";
+
+ const std::string entry_after =
+ R"(OpCapability Shader
+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
+)";
+
+ const std::string names_annots =
+ R"(OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+)";
+
+ const std::string new_annots =
+ R"(OpDecorate %_struct_55 Block
+OpMemberDecorate %_struct_55 0 Offset 0
+OpMemberDecorate %_struct_55 1 Offset 4
+OpDecorate %57 DescriptorSet 7
+OpDecorate %57 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+)";
+
+ const std::string consts_types_vars =
+ R"(%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_16_uint_128 = OpTypeArray %16 %uint_128
+%_ptr_UniformConstant__arr_16_uint_128 = OpTypePointer UniformConstant %_arr_16_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_16_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%24 = OpTypeSampler
+%_ptr_UniformConstant_24 = OpTypePointer UniformConstant %24
+%g_sAniso = OpVariable %_ptr_UniformConstant_24 UniformConstant
+%26 = OpTypeSampledImage %16
+%_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 new_consts_types_vars =
+ R"(%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%48 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_55 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_55 = OpTypePointer StorageBuffer %_struct_55
+%57 = OpVariable %_ptr_StorageBuffer__struct_55 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%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_56 = OpConstant %uint 56
+%103 = OpConstantNull %v4float
+)";
+
+ const std::string func_pt1 =
+ R"(%MainPs = OpFunction %void None %10
+%29 = OpLabel
+%30 = OpLoad %v2float %i_vTextureCoords
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%33 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %32
+%34 = OpLoad %16 %33
+%35 = OpLoad %24 %g_sAniso
+%36 = OpSampledImage %26 %34 %35
+)";
+
+ const std::string func_pt2_before =
+ R"(%37 = OpImageSampleImplicitLod %v4float %36 %30
+OpStore %_entryPointOutput_vColor %37
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_pt2_after =
+ R"(%40 = OpULessThan %bool %32 %uint_128
+OpSelectionMerge %41 None
+OpBranchConditional %40 %42 %43
+%42 = OpLabel
+%44 = OpLoad %16 %33
+%45 = OpSampledImage %26 %44 %35
+%46 = OpImageSampleImplicitLod %v4float %45 %30
+OpBranch %41
+%43 = OpLabel
+%102 = OpFunctionCall %void %47 %uint_56 %uint_0 %32 %uint_128
+OpBranch %41
+%41 = OpLabel
+%104 = OpPhi %v4float %46 %42 %103 %43
+OpStore %_entryPointOutput_vColor %104
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%47 = OpFunction %void None %48
+%49 = OpFunctionParameter %uint
+%50 = OpFunctionParameter %uint
+%51 = OpFunctionParameter %uint
+%52 = OpFunctionParameter %uint
+%53 = OpLabel
+%59 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_0
+%62 = OpAtomicIAdd %uint %59 %uint_4 %uint_0 %uint_9
+%63 = OpIAdd %uint %62 %uint_9
+%64 = OpArrayLength %uint %57 1
+%65 = OpULessThanEqual %bool %63 %64
+OpSelectionMerge %66 None
+OpBranchConditional %65 %67 %66
+%67 = OpLabel
+%68 = OpIAdd %uint %62 %uint_0
+%70 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %68
+OpStore %70 %uint_9
+%72 = OpIAdd %uint %62 %uint_1
+%73 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %72
+OpStore %73 %uint_23
+%75 = OpIAdd %uint %62 %uint_2
+%76 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %75
+OpStore %76 %49
+%78 = OpIAdd %uint %62 %uint_3
+%79 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %78
+OpStore %79 %uint_4
+%82 = OpLoad %v4float %gl_FragCoord
+%84 = OpBitcast %v4uint %82
+%85 = OpCompositeExtract %uint %84 0
+%86 = OpIAdd %uint %62 %uint_4
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %86
+OpStore %87 %85
+%88 = OpCompositeExtract %uint %84 1
+%90 = OpIAdd %uint %62 %uint_5
+%91 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %90
+OpStore %91 %88
+%93 = OpIAdd %uint %62 %uint_6
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %93
+OpStore %94 %50
+%96 = OpIAdd %uint %62 %uint_7
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %96
+OpStore %97 %51
+%99 = OpIAdd %uint %62 %uint_8
+%100 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %99
+OpStore %100 %52
+OpBranch %66
+%66 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(
+ entry_before + names_annots + consts_types_vars + func_pt1 +
+ func_pt2_before,
+ entry_after + names_annots + new_annots + consts_types_vars +
+ new_consts_types_vars + func_pt1 + func_pt2_after + output_func,
+ true, true);
+}
+
+TEST_F(InstBindlessTest, NoInstrumentConstIndexInbounds) {
+ // Texture2D g_tColor[128];
+ //
+ // 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[ 37 ].Sample(g_sAniso, i.vTextureCoords.xy);
+ // return ps_output;
+ // }
+
+ const std::string before =
+ 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 3
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 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
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_37 = OpConstant %int 37
+%15 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_15_uint_128 = OpTypeArray %15 %uint_128
+%_ptr_UniformConstant__arr_15_uint_128 = OpTypePointer UniformConstant %_arr_15_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_15_uint_128 UniformConstant
+%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
+%21 = OpTypeSampler
+%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21
+%g_sAniso = OpVariable %_ptr_UniformConstant_21 UniformConstant
+%23 = OpTypeSampledImage %15
+%_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
+%26 = OpLabel
+%27 = OpLoad %v2float %i_vTextureCoords
+%28 = OpAccessChain %_ptr_UniformConstant_15 %g_tColor %int_37
+%29 = OpLoad %15 %28
+%30 = OpLoad %21 %g_sAniso
+%31 = OpSampledImage %23 %29 %30
+%32 = OpImageSampleImplicitLod %v4float %31 %27
+OpStore %_entryPointOutput_vColor %32
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(before, before, true, true);
+}
+
+TEST_F(InstBindlessTest, InstrumentMultipleInstructions) {
+ // Texture2D g_tColor[128];
+ //
+ // layout(push_constant) cbuffer PerViewConstantBuffer_t
+ // {
+ // uint g_nDataIdx;
+ // uint g_nDataIdx2;
+ // };
+ //
+ // 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;
+ //
+ // float t = g_tColor[g_nDataIdx ].Sample(g_sAniso, i.vTextureCoords.xy);
+ // float t2 = g_tColor[g_nDataIdx2].Sample(g_sAniso, i.vTextureCoords.xy);
+ // ps_output.vColor = t + t2;
+ // return ps_output;
+ // }
+
+ const std::string defs_before =
+ 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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_17_uint_128 = OpTypeArray %17 %uint_128
+%_ptr_UniformConstant__arr_17_uint_128 = OpTypePointer UniformConstant %_arr_17_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_17_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17
+%25 = OpTypeSampler
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%g_sAniso = OpVariable %_ptr_UniformConstant_25 UniformConstant
+%27 = OpTypeSampledImage %17
+%_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_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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_struct_63 Block
+OpMemberDecorate %_struct_63 0 Offset 0
+OpMemberDecorate %_struct_63 1 Offset 4
+OpDecorate %65 DescriptorSet 7
+OpDecorate %65 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_17_uint_128 = OpTypeArray %17 %uint_128
+%_ptr_UniformConstant__arr_17_uint_128 = OpTypePointer UniformConstant %_arr_17_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_17_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17
+%25 = OpTypeSampler
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%g_sAniso = OpVariable %_ptr_UniformConstant_25 UniformConstant
+%27 = OpTypeSampledImage %17
+%_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_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%56 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
+%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%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_58 = OpConstant %uint 58
+%111 = OpConstantNull %v4float
+%uint_64 = OpConstant %uint 64
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %10
+%30 = OpLabel
+%31 = OpLoad %v2float %i_vTextureCoords
+%32 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%33 = OpLoad %uint %32
+%34 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %33
+%35 = OpLoad %17 %34
+%36 = OpLoad %25 %g_sAniso
+%37 = OpSampledImage %27 %35 %36
+%38 = OpImageSampleImplicitLod %v4float %37 %31
+%39 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+%40 = OpLoad %uint %39
+%41 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %40
+%42 = OpLoad %17 %41
+%43 = OpSampledImage %27 %42 %36
+%44 = OpImageSampleImplicitLod %v4float %43 %31
+%45 = OpFAdd %v4float %38 %44
+OpStore %_entryPointOutput_vColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %10
+%30 = OpLabel
+%31 = OpLoad %v2float %i_vTextureCoords
+%32 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%33 = OpLoad %uint %32
+%34 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %33
+%35 = OpLoad %17 %34
+%36 = OpLoad %25 %g_sAniso
+%37 = OpSampledImage %27 %35 %36
+%48 = OpULessThan %bool %33 %uint_128
+OpSelectionMerge %49 None
+OpBranchConditional %48 %50 %51
+%50 = OpLabel
+%52 = OpLoad %17 %34
+%53 = OpSampledImage %27 %52 %36
+%54 = OpImageSampleImplicitLod %v4float %53 %31
+OpBranch %49
+%51 = OpLabel
+%110 = OpFunctionCall %void %55 %uint_58 %uint_0 %33 %uint_128
+OpBranch %49
+%49 = OpLabel
+%112 = OpPhi %v4float %54 %50 %111 %51
+%39 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+%40 = OpLoad %uint %39
+%41 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %40
+%42 = OpLoad %17 %41
+%43 = OpSampledImage %27 %42 %36
+%113 = OpULessThan %bool %40 %uint_128
+OpSelectionMerge %114 None
+OpBranchConditional %113 %115 %116
+%115 = OpLabel
+%117 = OpLoad %17 %41
+%118 = OpSampledImage %27 %117 %36
+%119 = OpImageSampleImplicitLod %v4float %118 %31
+OpBranch %114
+%116 = OpLabel
+%121 = OpFunctionCall %void %55 %uint_64 %uint_0 %40 %uint_128
+OpBranch %114
+%114 = OpLabel
+%122 = OpPhi %v4float %119 %115 %111 %116
+%45 = OpFAdd %v4float %112 %122
+OpStore %_entryPointOutput_vColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%55 = OpFunction %void None %56
+%57 = OpFunctionParameter %uint
+%58 = OpFunctionParameter %uint
+%59 = OpFunctionParameter %uint
+%60 = OpFunctionParameter %uint
+%61 = OpLabel
+%67 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
+%70 = OpAtomicIAdd %uint %67 %uint_4 %uint_0 %uint_9
+%71 = OpIAdd %uint %70 %uint_9
+%72 = OpArrayLength %uint %65 1
+%73 = OpULessThanEqual %bool %71 %72
+OpSelectionMerge %74 None
+OpBranchConditional %73 %75 %74
+%75 = OpLabel
+%76 = OpIAdd %uint %70 %uint_0
+%78 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %76
+OpStore %78 %uint_9
+%80 = OpIAdd %uint %70 %uint_1
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %80
+OpStore %81 %uint_23
+%83 = OpIAdd %uint %70 %uint_2
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %83
+OpStore %84 %57
+%86 = OpIAdd %uint %70 %uint_3
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %86
+OpStore %87 %uint_4
+%90 = OpLoad %v4float %gl_FragCoord
+%92 = OpBitcast %v4uint %90
+%93 = OpCompositeExtract %uint %92 0
+%94 = OpIAdd %uint %70 %uint_4
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
+OpStore %95 %93
+%96 = OpCompositeExtract %uint %92 1
+%98 = OpIAdd %uint %70 %uint_5
+%99 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %98
+OpStore %99 %96
+%101 = OpIAdd %uint %70 %uint_6
+%102 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %101
+OpStore %102 %58
+%104 = OpIAdd %uint %70 %uint_7
+%105 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %104
+OpStore %105 %59
+%107 = OpIAdd %uint %70 %uint_8
+%108 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %107
+OpStore %108 %60
+OpBranch %74
+%74 = 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,
+ true);
+}
+
+TEST_F(InstBindlessTest, ReuseConstsTypesBuiltins) {
+ // This test verifies that the pass resuses existing constants, types
+ // and builtin variables. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %85 DescriptorSet 7
+OpDecorate %85 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_20_uint_128 = OpTypeArray %20 %uint_128
+%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%39 = OpTypeSampledImage %20
+%_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_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_83 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_83 = OpTypePointer StorageBuffer %_struct_83
+%85 = OpVariable %_ptr_StorageBuffer__struct_83 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_10 = OpConstant %uint 10
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_9 = OpConstant %uint 9
+%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
+%131 = OpConstantNull %v4float
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %10 DescriptorSet 7
+OpDecorate %10 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+OpDecorate %_struct_34 Block
+OpMemberDecorate %_struct_34 0 Offset 0
+OpMemberDecorate %_struct_34 1 Offset 4
+OpDecorate %74 DescriptorSet 7
+OpDecorate %74 Binding 0
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%26 = OpTypeSampler
+%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26
+%g_sAniso = OpVariable %_ptr_UniformConstant_26 UniformConstant
+%28 = OpTypeSampledImage %18
+%_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_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_34 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_34 = OpTypePointer StorageBuffer %_struct_34
+%10 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_10 = OpConstant %uint 10
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_9 = OpConstant %uint 9
+%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
+%50 = OpConstantNull %v4float
+%68 = OpTypeFunction %void %uint %uint %uint %uint
+%74 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
+%uint_82 = OpConstant %uint 82
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%67 = OpLoad %35 %g_sAniso
+%78 = OpLoad %20 %65
+%79 = OpSampledImage %39 %78 %67
+%71 = OpImageSampleImplicitLod %v4float %79 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %12
+%51 = OpLabel
+%52 = OpLoad %v2float %i_vTextureCoords
+%53 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%54 = OpLoad %uint %53
+%55 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %54
+%56 = OpLoad %26 %g_sAniso
+%57 = OpLoad %18 %55
+%58 = OpSampledImage %28 %57 %56
+%60 = OpULessThan %bool %54 %uint_128
+OpSelectionMerge %61 None
+OpBranchConditional %60 %62 %63
+%62 = OpLabel
+%64 = OpLoad %18 %55
+%65 = OpSampledImage %28 %64 %56
+%66 = OpImageSampleImplicitLod %v4float %65 %52
+OpBranch %61
+%63 = OpLabel
+%105 = OpFunctionCall %void %67 %uint_82 %uint_0 %54 %uint_128
+OpBranch %61
+%61 = OpLabel
+%106 = OpPhi %v4float %66 %62 %50 %63
+OpStore %_entryPointOutput_vColor %106
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%67 = OpFunction %void None %68
+%69 = OpFunctionParameter %uint
+%70 = OpFunctionParameter %uint
+%71 = OpFunctionParameter %uint
+%72 = OpFunctionParameter %uint
+%73 = OpLabel
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_0
+%76 = OpAtomicIAdd %uint %75 %uint_4 %uint_0 %uint_9
+%77 = OpIAdd %uint %76 %uint_9
+%78 = OpArrayLength %uint %74 1
+%79 = OpULessThanEqual %bool %77 %78
+OpSelectionMerge %80 None
+OpBranchConditional %79 %81 %80
+%81 = OpLabel
+%82 = OpIAdd %uint %76 %uint_0
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %82
+OpStore %83 %uint_9
+%84 = OpIAdd %uint %76 %uint_1
+%85 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %84
+OpStore %85 %uint_23
+%86 = OpIAdd %uint %76 %uint_2
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %86
+OpStore %87 %69
+%88 = OpIAdd %uint %76 %uint_3
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %88
+OpStore %89 %uint_4
+%90 = OpLoad %v4float %gl_FragCoord
+%91 = OpBitcast %v4uint %90
+%92 = OpCompositeExtract %uint %91 0
+%93 = OpIAdd %uint %76 %uint_4
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %93
+OpStore %94 %92
+%95 = OpCompositeExtract %uint %91 1
+%96 = OpIAdd %uint %76 %uint_5
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %96
+OpStore %97 %95
+%98 = OpIAdd %uint %76 %uint_6
+%99 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %98
+OpStore %99 %70
+%100 = OpIAdd %uint %76 %uint_7
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %100
+OpStore %101 %71
+%102 = OpIAdd %uint %76 %uint_8
+%103 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %102
+OpStore %103 %72
+OpBranch %80
+%80 = 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,
+ true);
+}
+
+TEST_F(InstBindlessTest, InstrumentOpImage) {
+ // This test verifies that the pass will correctly instrument shader
+ // using OpImage. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+OpCapability StorageImageReadWithoutFormat
+%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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%39 = OpTypeSampledImage %20
+%_arr_39_uint_128 = OpTypeArray %39 %uint_128
+%_ptr_UniformConstant__arr_39_uint_128 = OpTypePointer UniformConstant %_arr_39_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_39_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_39 = OpTypePointer UniformConstant %39
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpCapability StorageImageReadWithoutFormat
+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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_struct_51 Block
+OpMemberDecorate %_struct_51 0 Offset 0
+OpMemberDecorate %_struct_51 1 Offset 4
+OpDecorate %53 DescriptorSet 7
+OpDecorate %53 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%15 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%18 = OpTypeSampledImage %15
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%44 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_51 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_51 = OpTypePointer StorageBuffer %_struct_51
+%53 = OpVariable %_ptr_StorageBuffer__struct_51 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%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_51 = OpConstant %uint 51
+%99 = OpConstantNull %v4float
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2int %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_39 %g_tColor %64
+%66 = OpLoad %39 %65
+%75 = OpImage %20 %66
+%71 = OpImageRead %v4float %75 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %9
+%26 = OpLabel
+%27 = OpLoad %v2int %i_vTextureCoords
+%28 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%29 = OpLoad %uint %28
+%30 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %29
+%31 = OpLoad %18 %30
+%32 = OpImage %15 %31
+%36 = OpULessThan %bool %29 %uint_128
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %39
+%38 = OpLabel
+%40 = OpLoad %18 %30
+%41 = OpImage %15 %40
+%42 = OpImageRead %v4float %41 %27
+OpBranch %37
+%39 = OpLabel
+%98 = OpFunctionCall %void %43 %uint_51 %uint_0 %29 %uint_128
+OpBranch %37
+%37 = OpLabel
+%100 = OpPhi %v4float %42 %38 %99 %39
+OpStore %_entryPointOutput_vColor %100
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%43 = OpFunction %void None %44
+%45 = OpFunctionParameter %uint
+%46 = OpFunctionParameter %uint
+%47 = OpFunctionParameter %uint
+%48 = OpFunctionParameter %uint
+%49 = OpLabel
+%55 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_0
+%58 = OpAtomicIAdd %uint %55 %uint_4 %uint_0 %uint_9
+%59 = OpIAdd %uint %58 %uint_9
+%60 = OpArrayLength %uint %53 1
+%61 = OpULessThanEqual %bool %59 %60
+OpSelectionMerge %62 None
+OpBranchConditional %61 %63 %62
+%63 = OpLabel
+%64 = OpIAdd %uint %58 %uint_0
+%66 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %64
+OpStore %66 %uint_9
+%68 = OpIAdd %uint %58 %uint_1
+%69 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %68
+OpStore %69 %uint_23
+%71 = OpIAdd %uint %58 %uint_2
+%72 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %71
+OpStore %72 %45
+%74 = OpIAdd %uint %58 %uint_3
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %74
+OpStore %75 %uint_4
+%78 = OpLoad %v4float %gl_FragCoord
+%80 = OpBitcast %v4uint %78
+%81 = OpCompositeExtract %uint %80 0
+%82 = OpIAdd %uint %58 %uint_4
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %82
+OpStore %83 %81
+%84 = OpCompositeExtract %uint %80 1
+%86 = OpIAdd %uint %58 %uint_5
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %86
+OpStore %87 %84
+%89 = OpIAdd %uint %58 %uint_6
+%90 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %89
+OpStore %90 %46
+%92 = OpIAdd %uint %58 %uint_7
+%93 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %92
+OpStore %93 %47
+%95 = OpIAdd %uint %58 %uint_8
+%96 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %95
+OpStore %96 %48
+OpBranch %62
+%62 = 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,
+ true);
+}
+
+TEST_F(InstBindlessTest, InstrumentSampledImage) {
+ // This test verifies that the pass will correctly instrument shader
+ // using sampled image. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ 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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%39 = OpTypeSampledImage %20
+%_arr_39_uint_128 = OpTypeArray %39 %uint_128
+%_ptr_UniformConstant__arr_39_uint_128 = OpTypePointer UniformConstant %_arr_39_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_39_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_39 = OpTypePointer UniformConstant %39
+%_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_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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_struct_49 Block
+OpMemberDecorate %_struct_49 0 Offset 0
+OpMemberDecorate %_struct_49 1 Offset 4
+OpDecorate %51 DescriptorSet 7
+OpDecorate %51 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%15 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%18 = OpTypeSampledImage %15
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_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_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%42 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_49 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_49 = OpTypePointer StorageBuffer %_struct_49
+%51 = OpVariable %_ptr_StorageBuffer__struct_49 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%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_49 = OpConstant %uint 49
+%97 = OpConstantNull %v4float
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_39 %g_tColor %64
+%66 = OpLoad %39 %65
+%71 = OpImageSampleImplicitLod %v4float %66 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %9
+%26 = OpLabel
+%27 = OpLoad %v2float %i_vTextureCoords
+%28 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%29 = OpLoad %uint %28
+%30 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %29
+%31 = OpLoad %18 %30
+%35 = OpULessThan %bool %29 %uint_128
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %38
+%37 = OpLabel
+%39 = OpLoad %18 %30
+%40 = OpImageSampleImplicitLod %v4float %39 %27
+OpBranch %36
+%38 = OpLabel
+%96 = OpFunctionCall %void %41 %uint_49 %uint_0 %29 %uint_128
+OpBranch %36
+%36 = OpLabel
+%98 = OpPhi %v4float %40 %37 %97 %38
+OpStore %_entryPointOutput_vColor %98
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%41 = OpFunction %void None %42
+%43 = OpFunctionParameter %uint
+%44 = OpFunctionParameter %uint
+%45 = OpFunctionParameter %uint
+%46 = OpFunctionParameter %uint
+%47 = OpLabel
+%53 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_0
+%56 = OpAtomicIAdd %uint %53 %uint_4 %uint_0 %uint_9
+%57 = OpIAdd %uint %56 %uint_9
+%58 = OpArrayLength %uint %51 1
+%59 = OpULessThanEqual %bool %57 %58
+OpSelectionMerge %60 None
+OpBranchConditional %59 %61 %60
+%61 = OpLabel
+%62 = OpIAdd %uint %56 %uint_0
+%64 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %62
+OpStore %64 %uint_9
+%66 = OpIAdd %uint %56 %uint_1
+%67 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %66
+OpStore %67 %uint_23
+%69 = OpIAdd %uint %56 %uint_2
+%70 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %69
+OpStore %70 %43
+%72 = OpIAdd %uint %56 %uint_3
+%73 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %72
+OpStore %73 %uint_4
+%76 = OpLoad %v4float %gl_FragCoord
+%78 = OpBitcast %v4uint %76
+%79 = OpCompositeExtract %uint %78 0
+%80 = OpIAdd %uint %56 %uint_4
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %80
+OpStore %81 %79
+%82 = OpCompositeExtract %uint %78 1
+%84 = OpIAdd %uint %56 %uint_5
+%85 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %84
+OpStore %85 %82
+%87 = OpIAdd %uint %56 %uint_6
+%88 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %87
+OpStore %88 %44
+%90 = OpIAdd %uint %56 %uint_7
+%91 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %90
+OpStore %91 %45
+%93 = OpIAdd %uint %56 %uint_8
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %93
+OpStore %94 %46
+OpBranch %60
+%60 = 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,
+ true);
+}
+
+TEST_F(InstBindlessTest, InstrumentImageWrite) {
+ // This test verifies that the pass will correctly instrument shader
+ // doing bindless image write. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+OpCapability StorageImageWriteWithoutFormat
+%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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%80 = OpConstantNull %v4float
+%_arr_20_uint_128 = OpTypeArray %20 %uint_128
+%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpCapability StorageImageWriteWithoutFormat
+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 %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_struct_48 Block
+OpMemberDecorate %_struct_48 0 Offset 0
+OpMemberDecorate %_struct_48 1 Offset 4
+OpDecorate %50 DescriptorSet 7
+OpDecorate %50 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%19 = OpConstantNull %v4float
+%_arr_16_uint_128 = OpTypeArray %16 %uint_128
+%_ptr_UniformConstant__arr_16_uint_128 = OpTypePointer UniformConstant %_arr_16_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_16_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%41 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_48 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_48 = OpTypePointer StorageBuffer %_struct_48
+%50 = OpVariable %_ptr_StorageBuffer__struct_48 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%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_51 = OpConstant %uint 51
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2int %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%66 = OpLoad %20 %65
+OpImageWrite %66 %53 %80
+OpStore %_entryPointOutput_vColor %80
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %9
+%27 = OpLabel
+%28 = OpLoad %v2int %i_vTextureCoords
+%29 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%30 = OpLoad %uint %29
+%31 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %30
+%32 = OpLoad %16 %31
+%35 = OpULessThan %bool %30 %uint_128
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %38
+%37 = OpLabel
+%39 = OpLoad %16 %31
+OpImageWrite %39 %28 %19
+OpBranch %36
+%38 = OpLabel
+%95 = OpFunctionCall %void %40 %uint_51 %uint_0 %30 %uint_128
+OpBranch %36
+%36 = OpLabel
+OpStore %_entryPointOutput_vColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%40 = OpFunction %void None %41
+%42 = OpFunctionParameter %uint
+%43 = OpFunctionParameter %uint
+%44 = OpFunctionParameter %uint
+%45 = OpFunctionParameter %uint
+%46 = OpLabel
+%52 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_0
+%55 = OpAtomicIAdd %uint %52 %uint_4 %uint_0 %uint_9
+%56 = OpIAdd %uint %55 %uint_9
+%57 = OpArrayLength %uint %50 1
+%58 = OpULessThanEqual %bool %56 %57
+OpSelectionMerge %59 None
+OpBranchConditional %58 %60 %59
+%60 = OpLabel
+%61 = OpIAdd %uint %55 %uint_0
+%63 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %61
+OpStore %63 %uint_9
+%65 = OpIAdd %uint %55 %uint_1
+%66 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %65
+OpStore %66 %uint_23
+%68 = OpIAdd %uint %55 %uint_2
+%69 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %68
+OpStore %69 %42
+%71 = OpIAdd %uint %55 %uint_3
+%72 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %71
+OpStore %72 %uint_4
+%75 = OpLoad %v4float %gl_FragCoord
+%77 = OpBitcast %v4uint %75
+%78 = OpCompositeExtract %uint %77 0
+%79 = OpIAdd %uint %55 %uint_4
+%80 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %79
+OpStore %80 %78
+%81 = OpCompositeExtract %uint %77 1
+%83 = OpIAdd %uint %55 %uint_5
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %83
+OpStore %84 %81
+%86 = OpIAdd %uint %55 %uint_6
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %86
+OpStore %87 %43
+%89 = OpIAdd %uint %55 %uint_7
+%90 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %89
+OpStore %90 %44
+%92 = OpIAdd %uint %55 %uint_8
+%93 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %92
+OpStore %93 %45
+OpBranch %59
+%59 = 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,
+ true);
+}
+
+TEST_F(InstBindlessTest, InstrumentVertexSimple) {
+ // This test verifies that the pass will correctly instrument shader
+ // doing bindless image write. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+OpCapability Sampled1D
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %_ %coords2D
+OpSource GLSL 450
+OpName %main "main"
+OpName %lod "lod"
+OpName %coords1D "coords1D"
+OpName %gl_PerVertex "gl_PerVertex"
+OpMemberName %gl_PerVertex 0 "gl_Position"
+OpMemberName %gl_PerVertex 1 "gl_PointSize"
+OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+OpName %_ ""
+OpName %texSampler1D "texSampler1D"
+OpName %foo "foo"
+OpMemberName %foo 0 "g_idx"
+OpName %__0 ""
+OpName %coords2D "coords2D"
+OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+OpDecorate %gl_PerVertex Block
+OpDecorate %texSampler1D DescriptorSet 0
+OpDecorate %texSampler1D Binding 3
+OpMemberDecorate %foo 0 Offset 0
+OpDecorate %foo Block
+OpDecorate %__0 DescriptorSet 0
+OpDecorate %__0 Binding 5
+OpDecorate %coords2D Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_3 = OpConstant %float 3
+%float_1_78900003 = OpConstant %float 1.78900003
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+%_ = OpVariable %_ptr_Output_gl_PerVertex Output
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%21 = OpTypeImage %float 1D 0 0 0 1 Unknown
+%22 = OpTypeSampledImage %21
+%uint_128 = OpConstant %uint 128
+%_arr_22_uint_128 = OpTypeArray %22 %uint_128
+%_ptr_UniformConstant__arr_22_uint_128 = OpTypePointer UniformConstant %_arr_22_uint_128
+%texSampler1D = OpVariable %_ptr_UniformConstant__arr_22_uint_128 UniformConstant
+%foo = OpTypeStruct %int
+%_ptr_Uniform_foo = OpTypePointer Uniform %foo
+%__0 = OpVariable %_ptr_Uniform_foo Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%_ptr_UniformConstant_22 = OpTypePointer UniformConstant %22
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%coords2D = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpCapability Sampled1D
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %_ %coords2D %gl_VertexID %gl_InstanceID
+OpSource GLSL 450
+OpName %main "main"
+OpName %lod "lod"
+OpName %coords1D "coords1D"
+OpName %gl_PerVertex "gl_PerVertex"
+OpMemberName %gl_PerVertex 0 "gl_Position"
+OpMemberName %gl_PerVertex 1 "gl_PointSize"
+OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+OpName %_ ""
+OpName %texSampler1D "texSampler1D"
+OpName %foo "foo"
+OpMemberName %foo 0 "g_idx"
+OpName %__0 ""
+OpName %coords2D "coords2D"
+OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+OpDecorate %gl_PerVertex Block
+OpDecorate %texSampler1D DescriptorSet 0
+OpDecorate %texSampler1D Binding 3
+OpMemberDecorate %foo 0 Offset 0
+OpDecorate %foo Block
+OpDecorate %__0 DescriptorSet 0
+OpDecorate %__0 Binding 5
+OpDecorate %coords2D Location 0
+OpDecorate %_struct_61 Block
+OpMemberDecorate %_struct_61 0 Offset 0
+OpMemberDecorate %_struct_61 1 Offset 4
+OpDecorate %63 DescriptorSet 7
+OpDecorate %63 Binding 0
+OpDecorate %gl_VertexID BuiltIn VertexId
+OpDecorate %gl_InstanceID BuiltIn InstanceId
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_3 = OpConstant %float 3
+%float_1_78900003 = OpConstant %float 1.78900003
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+%_ = OpVariable %_ptr_Output_gl_PerVertex Output
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%24 = OpTypeImage %float 1D 0 0 0 1 Unknown
+%25 = OpTypeSampledImage %24
+%uint_128 = OpConstant %uint 128
+%_arr_25_uint_128 = OpTypeArray %25 %uint_128
+%_ptr_UniformConstant__arr_25_uint_128 = OpTypePointer UniformConstant %_arr_25_uint_128
+%texSampler1D = OpVariable %_ptr_UniformConstant__arr_25_uint_128 UniformConstant
+%foo = OpTypeStruct %int
+%_ptr_Uniform_foo = OpTypePointer Uniform %foo
+%__0 = OpVariable %_ptr_Uniform_foo Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%coords2D = OpVariable %_ptr_Input_v2float Input
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%54 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_61 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_61 = OpTypePointer StorageBuffer %_struct_61
+%63 = OpVariable %_ptr_StorageBuffer__struct_61 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%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_uint = OpTypePointer Input %uint
+%gl_VertexID = OpVariable %_ptr_Input_uint Input
+%gl_InstanceID = OpVariable %_ptr_Input_uint Input
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_74 = OpConstant %uint 74
+%106 = OpConstantNull %v4float
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %3
+%5 = OpLabel
+%lod = OpVariable %_ptr_Function_float Function
+%coords1D = OpVariable %_ptr_Function_float Function
+OpStore %lod %float_3
+OpStore %coords1D %float_1_78900003
+%31 = OpAccessChain %_ptr_Uniform_int %__0 %int_0
+%32 = OpLoad %int %31
+%34 = OpAccessChain %_ptr_UniformConstant_22 %texSampler1D %32
+%35 = OpLoad %22 %34
+%36 = OpLoad %float %coords1D
+%37 = OpLoad %float %lod
+%38 = OpImageSampleExplicitLod %v4float %35 %36 Lod %37
+%40 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+OpStore %40 %38
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %12
+%35 = OpLabel
+%lod = OpVariable %_ptr_Function_float Function
+%coords1D = OpVariable %_ptr_Function_float Function
+OpStore %lod %float_3
+OpStore %coords1D %float_1_78900003
+%36 = OpAccessChain %_ptr_Uniform_int %__0 %int_0
+%37 = OpLoad %int %36
+%38 = OpAccessChain %_ptr_UniformConstant_25 %texSampler1D %37
+%39 = OpLoad %25 %38
+%40 = OpLoad %float %coords1D
+%41 = OpLoad %float %lod
+%46 = OpULessThan %bool %37 %uint_128
+OpSelectionMerge %47 None
+OpBranchConditional %46 %48 %49
+%48 = OpLabel
+%50 = OpLoad %25 %38
+%51 = OpImageSampleExplicitLod %v4float %50 %40 Lod %41
+OpBranch %47
+%49 = OpLabel
+%52 = OpBitcast %uint %37
+%105 = OpFunctionCall %void %53 %uint_74 %uint_0 %52 %uint_128
+OpBranch %47
+%47 = OpLabel
+%107 = OpPhi %v4float %51 %48 %106 %49
+%43 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+OpStore %43 %107
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%53 = OpFunction %void None %54
+%55 = OpFunctionParameter %uint
+%56 = OpFunctionParameter %uint
+%57 = OpFunctionParameter %uint
+%58 = OpFunctionParameter %uint
+%59 = OpLabel
+%65 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_0
+%68 = OpAtomicIAdd %uint %65 %uint_4 %uint_0 %uint_9
+%69 = OpIAdd %uint %68 %uint_9
+%70 = OpArrayLength %uint %63 1
+%71 = OpULessThanEqual %bool %69 %70
+OpSelectionMerge %72 None
+OpBranchConditional %71 %73 %72
+%73 = OpLabel
+%74 = OpIAdd %uint %68 %uint_0
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %74
+OpStore %75 %uint_9
+%77 = OpIAdd %uint %68 %uint_1
+%78 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %77
+OpStore %78 %uint_23
+%80 = OpIAdd %uint %68 %uint_2
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %80
+OpStore %81 %55
+%83 = OpIAdd %uint %68 %uint_3
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %83
+OpStore %84 %uint_0
+%87 = OpLoad %uint %gl_VertexID
+%88 = OpIAdd %uint %68 %uint_4
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %88
+OpStore %89 %87
+%91 = OpLoad %uint %gl_InstanceID
+%93 = OpIAdd %uint %68 %uint_5
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %93
+OpStore %94 %91
+%96 = OpIAdd %uint %68 %uint_6
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %96
+OpStore %97 %56
+%99 = OpIAdd %uint %68 %uint_7
+%100 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %99
+OpStore %100 %57
+%102 = OpIAdd %uint %68 %uint_8
+%103 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %102
+OpStore %103 %58
+OpBranch %72
+%72 = 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,
+ true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// TODO(greg-lunarg): Come up with cases to put here :)
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/ir_builder.cpp b/test/opt/ir_builder.cpp
index 41d7f57..8231bdc 100644
--- a/test/opt/ir_builder.cpp
+++ b/test/opt/ir_builder.cpp
@@ -317,20 +317,20 @@
InstructionBuilder builder(context.get(),
&*context->module()->begin()->begin()->begin());
- EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(13));
- EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(-1));
+ EXPECT_NE(nullptr, builder.GetUintConstant(13));
+ EXPECT_NE(nullptr, builder.GetSintConstant(-1));
// Try adding the same constants again to make sure they aren't added.
- EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(13));
- EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(-1));
+ EXPECT_NE(nullptr, builder.GetUintConstant(13));
+ EXPECT_NE(nullptr, builder.GetSintConstant(-1));
// Try adding different constants to make sure the type is reused.
- EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(1));
- EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(34));
+ EXPECT_NE(nullptr, builder.GetUintConstant(1));
+ EXPECT_NE(nullptr, builder.GetSintConstant(34));
// Try adding 0 as both signed and unsigned.
- EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(0));
- EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(0));
+ EXPECT_NE(nullptr, builder.GetUintConstant(0));
+ EXPECT_NE(nullptr, builder.GetSintConstant(0));
Match(text, context.get());
}
@@ -362,25 +362,25 @@
InstructionBuilder builder(context.get(),
&*context->module()->begin()->begin()->begin());
- Instruction* const_1 = builder.Add32BitUnsignedIntegerConstant(13);
- Instruction* const_2 = builder.Add32BitSignedIntegerConstant(-1);
+ Instruction* const_1 = builder.GetUintConstant(13);
+ Instruction* const_2 = builder.GetSintConstant(-1);
EXPECT_NE(nullptr, const_1);
EXPECT_NE(nullptr, const_2);
// Try adding the same constants again to make sure they aren't added.
- EXPECT_EQ(const_1, builder.Add32BitUnsignedIntegerConstant(13));
- EXPECT_EQ(const_2, builder.Add32BitSignedIntegerConstant(-1));
+ EXPECT_EQ(const_1, builder.GetUintConstant(13));
+ EXPECT_EQ(const_2, builder.GetSintConstant(-1));
- Instruction* const_3 = builder.Add32BitUnsignedIntegerConstant(1);
- Instruction* const_4 = builder.Add32BitSignedIntegerConstant(34);
+ Instruction* const_3 = builder.GetUintConstant(1);
+ Instruction* const_4 = builder.GetSintConstant(34);
// Try adding different constants to make sure the type is reused.
EXPECT_NE(nullptr, const_3);
EXPECT_NE(nullptr, const_4);
- Instruction* const_5 = builder.Add32BitUnsignedIntegerConstant(0);
- Instruction* const_6 = builder.Add32BitSignedIntegerConstant(0);
+ Instruction* const_5 = builder.GetUintConstant(0);
+ Instruction* const_6 = builder.GetSintConstant(0);
// Try adding 0 as both signed and unsigned.
EXPECT_NE(nullptr, const_5);
diff --git a/test/opt/loop_optimizations/peeling.cpp b/test/opt/loop_optimizations/peeling.cpp
index 7c6a84e..10d8add 100644
--- a/test/opt/loop_optimizations/peeling.cpp
+++ b/test/opt/loop_optimizations/peeling.cpp
@@ -132,7 +132,7 @@
} else {
InstructionBuilder builder(context.get(), &*f.begin());
// Exit condition.
- loop_count = builder.Add32BitSignedIntegerConstant(10);
+ loop_count = builder.GetSintConstant(10);
}
LoopPeeling peel(&*ld.begin(), loop_count);
@@ -494,7 +494,7 @@
InstructionBuilder builder(context.get(), &*f.begin());
// Exit condition.
- Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
+ Instruction* ten_cst = builder.GetSintConstant(10);
LoopPeeling peel(&*ld.begin(), ten_cst);
EXPECT_TRUE(peel.CanPeelLoop());
@@ -548,7 +548,7 @@
InstructionBuilder builder(context.get(), &*f.begin());
// Exit condition.
- Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
+ Instruction* ten_cst = builder.GetSintConstant(10);
LoopPeeling peel(&*ld.begin(), ten_cst);
EXPECT_TRUE(peel.CanPeelLoop());
@@ -604,7 +604,7 @@
InstructionBuilder builder(context.get(), &*f.begin());
// Exit condition.
- Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
+ Instruction* ten_cst = builder.GetSintConstant(10);
LoopPeeling peel(&*ld.begin(), ten_cst,
context->get_def_use_mgr()->GetDef(22));
@@ -657,7 +657,7 @@
InstructionBuilder builder(context.get(), &*f.begin());
// Exit condition.
- Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
+ Instruction* ten_cst = builder.GetSintConstant(10);
LoopPeeling peel(&*ld.begin(), ten_cst,
context->get_def_use_mgr()->GetDef(22));
@@ -918,7 +918,7 @@
EXPECT_EQ(ld.NumLoops(), 1u);
InstructionBuilder builder(context.get(), &*f.begin());
// Exit condition.
- Instruction* ten_cst = builder.Add32BitUnsignedIntegerConstant(10);
+ Instruction* ten_cst = builder.GetUintConstant(10);
LoopPeeling peel(&*ld.begin(), ten_cst);
EXPECT_TRUE(peel.CanPeelLoop());
@@ -968,7 +968,7 @@
InstructionBuilder builder(context.get(), &*f.begin());
// Exit condition.
- Instruction* ten_cst = builder.Add32BitUnsignedIntegerConstant(10);
+ Instruction* ten_cst = builder.GetUintConstant(10);
LoopPeeling peel(&*ld.begin(), ten_cst);
EXPECT_TRUE(peel.CanPeelLoop());