aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgreg-lunarg <greg@lunarg.com>2020-03-12 07:19:52 -0600
committerGitHub <noreply@github.com>2020-03-12 09:19:52 -0400
commit1fe9bcc10824c1fa35bd9b697188340132d39213 (patch)
tree6f82277f348a8d623ec718a5e651d19ac49b1fd1
parent6428ad05e706567945faf0fa0ae6257bb6006733 (diff)
downloadspirv-tools-1fe9bcc10824c1fa35bd9b697188340132d39213.tar.gz
Instrument: Debug Printf support (#3215)
Create a pass to instrument OpDebugPrintf instructions. This pass replaces all OpDebugPrintf instructions with instructions to write a record containing the string id and the all specified values into a special printf output buffer (if space allows). This pass is designed to support the printf validation in the Vulkan validation layers. Fixes #3210
-rw-r--r--Android.mk1
-rw-r--r--BUILD.gn2
-rw-r--r--include/spirv-tools/instrument.hpp3
-rw-r--r--include/spirv-tools/optimizer.hpp12
-rw-r--r--source/enum_set.h15
-rw-r--r--source/opt/CMakeLists.txt2
-rw-r--r--source/opt/feature_manager.cpp10
-rw-r--r--source/opt/feature_manager.h6
-rw-r--r--source/opt/inst_debug_printf_pass.cpp266
-rw-r--r--source/opt/inst_debug_printf_pass.h96
-rw-r--r--source/opt/instrument_pass.cpp53
-rw-r--r--source/opt/instrument_pass.h22
-rw-r--r--source/opt/optimizer.cpp8
-rw-r--r--source/opt/passes.h1
-rw-r--r--test/opt/CMakeLists.txt1
-rw-r--r--test/opt/inst_debug_printf_test.cpp215
16 files changed, 693 insertions, 20 deletions
diff --git a/Android.mk b/Android.mk
index db4f43bd..eec709af 100644
--- a/Android.mk
+++ b/Android.mk
@@ -119,6 +119,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/inline_opaque_pass.cpp \
source/opt/inst_bindless_check_pass.cpp \
source/opt/inst_buff_addr_check_pass.cpp \
+ source/opt/inst_debug_printf_pass.cpp \
source/opt/instruction.cpp \
source/opt/instruction_list.cpp \
source/opt/instrument_pass.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 1337059d..d3107fd7 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -590,6 +590,8 @@ static_library("spvtools_opt") {
"source/opt/inst_bindless_check_pass.h",
"source/opt/inst_buff_addr_check_pass.cpp",
"source/opt/inst_buff_addr_check_pass.h",
+ "source/opt/inst_debug_printf_pass.cpp",
+ "source/opt/inst_debug_printf_pass.h",
"source/opt/instruction.cpp",
"source/opt/instruction.h",
"source/opt/instruction_list.cpp",
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index 2dcb3331..d3180e44 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -208,6 +208,9 @@ static const int kDebugInputBindingBindless = 1;
// The binding for the input buffer read by InstBuffAddrCheckPass.
static const int kDebugInputBindingBuffAddr = 2;
+// This is the output buffer written by InstDebugPrintfPass.
+static const int kDebugOutputPrintfStream = 3;
+
// Bindless Validation Input Buffer Format
//
// An input buffer for bindless validation consists of a single array of
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index c31ccef8..b9049232 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -791,6 +791,18 @@ Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t desc_set,
uint32_t shader_id,
uint32_t version = 2);
+// Create a pass to instrument OpDebugPrintf instructions.
+// This pass replaces all OpDebugPrintf instructions with instructions to write
+// a record containing the string id and the all specified values into a special
+// printf output buffer (if space allows). This pass is designed to support
+// the printf validation in the Vulkan validation layers.
+//
+// The instrumentation will 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.
+Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
+ uint32_t shader_id);
+
// Create a pass to upgrade to the VulkanKHR memory model.
// This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
// Additionally, it modifies memory, image, atomic and barrier operations to
diff --git a/source/enum_set.h b/source/enum_set.h
index 2e7046d4..d4d31e33 100644
--- a/source/enum_set.h
+++ b/source/enum_set.h
@@ -93,6 +93,10 @@ class EnumSet {
// enum value is already in the set.
void Add(EnumType c) { AddWord(ToWord(c)); }
+ // Removes the given enum value from the set. This has no effect if the
+ // enum value is not in the set.
+ void Remove(EnumType c) { RemoveWord(ToWord(c)); }
+
// Returns true if this enum value is in the set.
bool Contains(EnumType c) const { return ContainsWord(ToWord(c)); }
@@ -141,6 +145,17 @@ class EnumSet {
}
}
+ // Removes the given enum value (as a 32-bit word) from the set. This has no
+ // effect if the enum value is not in the set.
+ void RemoveWord(uint32_t word) {
+ if (auto new_bits = AsMask(word)) {
+ mask_ &= ~new_bits;
+ } else {
+ auto itr = Overflow().find(word);
+ if (itr != Overflow().end()) Overflow().erase(itr);
+ }
+ }
+
// Returns true if the enum represented as a 32-bit word is in the set.
bool ContainsWord(uint32_t word) const {
// We shouldn't call Overflow() since this is a const method.
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 0f719cb9..1428c746 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -58,6 +58,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
inline_pass.h
inst_bindless_check_pass.h
inst_buff_addr_check_pass.h
+ inst_debug_printf_pass.h
instruction.h
instruction_list.h
instrument_pass.h
@@ -164,6 +165,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
inline_pass.cpp
inst_bindless_check_pass.cpp
inst_buff_addr_check_pass.cpp
+ inst_debug_printf_pass.cpp
instruction.cpp
instruction_list.cpp
instrument_pass.cpp
diff --git a/source/opt/feature_manager.cpp b/source/opt/feature_manager.cpp
index 63d50b6d..b4d6f1ba 100644
--- a/source/opt/feature_manager.cpp
+++ b/source/opt/feature_manager.cpp
@@ -47,6 +47,11 @@ void FeatureManager::AddExtension(Instruction* ext) {
}
}
+void FeatureManager::RemoveExtension(Extension ext) {
+ if (!extensions_.Contains(ext)) return;
+ extensions_.Remove(ext);
+}
+
void FeatureManager::AddCapability(SpvCapability cap) {
if (capabilities_.Contains(cap)) return;
@@ -60,6 +65,11 @@ void FeatureManager::AddCapability(SpvCapability cap) {
}
}
+void FeatureManager::RemoveCapability(SpvCapability cap) {
+ if (!capabilities_.Contains(cap)) return;
+ capabilities_.Remove(cap);
+}
+
void FeatureManager::AddCapabilities(Module* module) {
for (Instruction& inst : module->capabilities()) {
AddCapability(static_cast<SpvCapability>(inst.GetSingleWordInOperand(0)));
diff --git a/source/opt/feature_manager.h b/source/opt/feature_manager.h
index 2fe32910..881d5e60 100644
--- a/source/opt/feature_manager.h
+++ b/source/opt/feature_manager.h
@@ -30,11 +30,17 @@ class FeatureManager {
// Returns true if |ext| is an enabled extension in the module.
bool HasExtension(Extension ext) const { return extensions_.Contains(ext); }
+ // Removes the given |extension| from the current FeatureManager.
+ void RemoveExtension(Extension extension);
+
// Returns true if |cap| is an enabled capability in the module.
bool HasCapability(SpvCapability cap) const {
return capabilities_.Contains(cap);
}
+ // Removes the given |capability| from the current FeatureManager.
+ void RemoveCapability(SpvCapability capability);
+
// Analyzes |module| and records enabled extensions and capabilities.
void Analyze(Module* module);
diff --git a/source/opt/inst_debug_printf_pass.cpp b/source/opt/inst_debug_printf_pass.cpp
new file mode 100644
index 00000000..c0e6bc3f
--- /dev/null
+++ b/source/opt/inst_debug_printf_pass.cpp
@@ -0,0 +1,266 @@
+// Copyright (c) 2020 The Khronos Group Inc.
+// Copyright (c) 2020 Valve Corporation
+// Copyright (c) 2020 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_debug_printf_pass.h"
+
+#include "spirv/unified1/NonSemanticDebugPrintf.h"
+
+namespace spvtools {
+namespace opt {
+
+void InstDebugPrintfPass::GenOutputValues(Instruction* val_inst,
+ std::vector<uint32_t>* val_ids,
+ InstructionBuilder* builder) {
+ uint32_t val_ty_id = val_inst->type_id();
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Type* val_ty = type_mgr->GetType(val_ty_id);
+ switch (val_ty->kind()) {
+ case analysis::Type::kVector: {
+ analysis::Vector* v_ty = val_ty->AsVector();
+ const analysis::Type* c_ty = v_ty->element_type();
+ uint32_t c_ty_id = type_mgr->GetId(c_ty);
+ for (uint32_t c = 0; c < v_ty->element_count(); ++c) {
+ Instruction* c_inst = builder->AddIdLiteralOp(
+ c_ty_id, SpvOpCompositeExtract, val_inst->result_id(), c);
+ GenOutputValues(c_inst, val_ids, builder);
+ }
+ return;
+ }
+ case analysis::Type::kBool: {
+ // Select between uint32 zero or one
+ uint32_t zero_id = builder->GetUintConstantId(0);
+ uint32_t one_id = builder->GetUintConstantId(1);
+ Instruction* sel_inst = builder->AddTernaryOp(
+ GetUintId(), SpvOpSelect, val_inst->result_id(), one_id, zero_id);
+ val_ids->push_back(sel_inst->result_id());
+ return;
+ }
+ case analysis::Type::kFloat: {
+ analysis::Float* f_ty = val_ty->AsFloat();
+ switch (f_ty->width()) {
+ case 16: {
+ // Convert float16 to float32 and recurse
+ Instruction* f32_inst = builder->AddUnaryOp(
+ GetFloatId(), SpvOpFConvert, val_inst->result_id());
+ GenOutputValues(f32_inst, val_ids, builder);
+ return;
+ }
+ case 64: {
+ // Bitcast float64 to uint64 and recurse
+ Instruction* ui64_inst = builder->AddUnaryOp(
+ GetUint64Id(), SpvOpBitcast, val_inst->result_id());
+ GenOutputValues(ui64_inst, val_ids, builder);
+ return;
+ }
+ case 32: {
+ // Bitcase float32 to uint32
+ Instruction* bc_inst = builder->AddUnaryOp(GetUintId(), SpvOpBitcast,
+ val_inst->result_id());
+ val_ids->push_back(bc_inst->result_id());
+ return;
+ }
+ default:
+ assert(false && "unsupported float width");
+ return;
+ }
+ }
+ case analysis::Type::kInteger: {
+ analysis::Integer* i_ty = val_ty->AsInteger();
+ switch (i_ty->width()) {
+ case 64: {
+ Instruction* ui64_inst = val_inst;
+ if (i_ty->IsSigned()) {
+ // Bitcast sint64 to uint64
+ ui64_inst = builder->AddUnaryOp(GetUint64Id(), SpvOpBitcast,
+ val_inst->result_id());
+ }
+ // Break uint64 into 2x uint32
+ Instruction* lo_ui64_inst = builder->AddUnaryOp(
+ GetUintId(), SpvOpUConvert, ui64_inst->result_id());
+ Instruction* rshift_ui64_inst = builder->AddBinaryOp(
+ GetUint64Id(), SpvOpShiftRightLogical, ui64_inst->result_id(),
+ builder->GetUintConstantId(32));
+ Instruction* hi_ui64_inst = builder->AddUnaryOp(
+ GetUintId(), SpvOpUConvert, rshift_ui64_inst->result_id());
+ val_ids->push_back(lo_ui64_inst->result_id());
+ val_ids->push_back(hi_ui64_inst->result_id());
+ return;
+ }
+ case 8: {
+ Instruction* ui8_inst = val_inst;
+ if (i_ty->IsSigned()) {
+ // Bitcast sint8 to uint8
+ ui8_inst = builder->AddUnaryOp(GetUint8Id(), SpvOpBitcast,
+ val_inst->result_id());
+ }
+ // Convert uint8 to uint32
+ Instruction* ui32_inst = builder->AddUnaryOp(
+ GetUintId(), SpvOpUConvert, ui8_inst->result_id());
+ val_ids->push_back(ui32_inst->result_id());
+ return;
+ }
+ case 32: {
+ Instruction* ui32_inst = val_inst;
+ if (i_ty->IsSigned()) {
+ // Bitcast sint32 to uint32
+ ui32_inst = builder->AddUnaryOp(GetUintId(), SpvOpBitcast,
+ val_inst->result_id());
+ }
+ // uint32 needs no further processing
+ val_ids->push_back(ui32_inst->result_id());
+ return;
+ }
+ default:
+ // TODO(greg-lunarg): Support non-32-bit int
+ assert(false && "unsupported int width");
+ return;
+ }
+ }
+ default:
+ assert(false && "unsupported type");
+ return;
+ }
+}
+
+void InstDebugPrintfPass::GenOutputCode(
+ Instruction* printf_inst, uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+ BasicBlock* back_blk_ptr = &*new_blocks->back();
+ InstructionBuilder builder(
+ context(), back_blk_ptr,
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ // Gen debug printf record validation-specific values. The format string
+ // will have its id written. Vectors will need to be broken down into
+ // component values. float16 will need to be converted to float32. Pointer
+ // and uint64 will need to be converted to two uint32 values. float32 will
+ // need to be bitcast to uint32. int32 will need to be bitcast to uint32.
+ std::vector<uint32_t> val_ids;
+ bool is_first_operand = false;
+ printf_inst->ForEachInId(
+ [&is_first_operand, &val_ids, &builder, this](const uint32_t* iid) {
+ // skip set operand
+ if (!is_first_operand) {
+ is_first_operand = true;
+ return;
+ }
+ Instruction* opnd_inst = get_def_use_mgr()->GetDef(*iid);
+ if (opnd_inst->opcode() == SpvOpString) {
+ uint32_t string_id_id = builder.GetUintConstantId(*iid);
+ val_ids.push_back(string_id_id);
+ } else {
+ GenOutputValues(opnd_inst, &val_ids, &builder);
+ }
+ });
+ GenDebugStreamWrite(uid2offset_[printf_inst->unique_id()], stage_idx, val_ids,
+ &builder);
+ context()->KillInst(printf_inst);
+}
+
+void InstDebugPrintfPass::GenDebugPrintfCode(
+ BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+ // If not DebugPrintf OpExtInst, return.
+ Instruction* printf_inst = &*ref_inst_itr;
+ if (printf_inst->opcode() != SpvOpExtInst) return;
+ if (printf_inst->GetSingleWordInOperand(0) != ext_inst_printf_id_) return;
+ if (printf_inst->GetSingleWordInOperand(1) !=
+ NonSemanticDebugPrintfDebugPrintf)
+ return;
+ // Initialize DefUse manager before dismantling module
+ (void)get_def_use_mgr();
+ // Move original block's preceding instructions into first new block
+ std::unique_ptr<BasicBlock> new_blk_ptr;
+ MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ // Generate instructions to output printf args to printf buffer
+ GenOutputCode(printf_inst, stage_idx, new_blocks);
+ // Caller expects at least two blocks with last block containing remaining
+ // code, so end block after instrumentation, create remainder block, and
+ // branch to it
+ uint32_t rem_blk_id = TakeNextId();
+ std::unique_ptr<Instruction> rem_label(NewLabel(rem_blk_id));
+ BasicBlock* back_blk_ptr = &*new_blocks->back();
+ InstructionBuilder builder(
+ context(), back_blk_ptr,
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ (void)builder.AddBranch(rem_blk_id);
+ // Gen remainder block
+ new_blk_ptr.reset(new BasicBlock(std::move(rem_label)));
+ builder.SetInsertPoint(&*new_blk_ptr);
+ // Move original block's remaining code into remainder block and add
+ // to new blocks
+ MovePostludeCode(ref_block_itr, &*new_blk_ptr);
+ new_blocks->push_back(std::move(new_blk_ptr));
+}
+
+void InstDebugPrintfPass::InitializeInstDebugPrintf() {
+ // Initialize base class
+ InitializeInstrument();
+}
+
+Pass::Status InstDebugPrintfPass::ProcessImpl() {
+ // Perform printf instrumentation on each entry point function in module
+ InstProcessFunction pfn =
+ [this](BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+ return GenDebugPrintfCode(ref_inst_itr, ref_block_itr, stage_idx,
+ new_blocks);
+ };
+ (void)InstProcessEntryPointCallTree(pfn);
+ // Remove DebugPrintf OpExtInstImport instruction
+ Instruction* ext_inst_import_inst =
+ get_def_use_mgr()->GetDef(ext_inst_printf_id_);
+ context()->KillInst(ext_inst_import_inst);
+ // If no remaining non-semantic instruction sets, remove non-semantic debug
+ // info extension from module and feature manager
+ bool non_sem_set_seen = false;
+ for (auto c_itr = context()->module()->ext_inst_import_begin();
+ c_itr != context()->module()->ext_inst_import_end(); ++c_itr) {
+ const char* set_name =
+ reinterpret_cast<const char*>(&c_itr->GetInOperand(0).words[0]);
+ const char* non_sem_str = "NonSemantic.";
+ if (!strncmp(set_name, non_sem_str, strlen(non_sem_str))) {
+ non_sem_set_seen = true;
+ break;
+ }
+ }
+ if (!non_sem_set_seen) {
+ for (auto c_itr = context()->module()->extension_begin();
+ c_itr != context()->module()->extension_end(); ++c_itr) {
+ const char* ext_name =
+ reinterpret_cast<const char*>(&c_itr->GetInOperand(0).words[0]);
+ if (!strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
+ context()->KillInst(&*c_itr);
+ break;
+ }
+ }
+ context()->get_feature_mgr()->RemoveExtension(kSPV_KHR_non_semantic_info);
+ }
+ return Status::SuccessWithChange;
+}
+
+Pass::Status InstDebugPrintfPass::Process() {
+ ext_inst_printf_id_ =
+ get_module()->GetExtInstImportId("NonSemantic.DebugPrintf");
+ if (ext_inst_printf_id_ == 0) return Status::SuccessWithoutChange;
+ InitializeInstDebugPrintf();
+ return ProcessImpl();
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/inst_debug_printf_pass.h b/source/opt/inst_debug_printf_pass.h
new file mode 100644
index 00000000..2968a203
--- /dev/null
+++ b/source/opt/inst_debug_printf_pass.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2020 The Khronos Group Inc.
+// Copyright (c) 2020 Valve Corporation
+// Copyright (c) 2020 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_DEBUG_PRINTF_PASS_H_
+#define LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
+
+#include "instrument_pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This class/pass is designed to support the debug printf GPU-assisted layer
+// of https://github.com/KhronosGroup/Vulkan-ValidationLayers. Its internal and
+// external design may change as the layer evolves.
+class InstDebugPrintfPass : public InstrumentPass {
+ public:
+ // For test harness only
+ InstDebugPrintfPass()
+ : InstrumentPass(7, 23, kInstValidationIdDebugPrintf, 2) {}
+ // For all other interfaces
+ InstDebugPrintfPass(uint32_t desc_set, uint32_t shader_id)
+ : InstrumentPass(desc_set, shader_id, kInstValidationIdDebugPrintf, 2) {}
+
+ ~InstDebugPrintfPass() override = default;
+
+ // See optimizer.hpp for pass user documentation.
+ Status Process() override;
+
+ const char* name() const override { return "inst-printf-pass"; }
+
+ private:
+ // Generate instructions for OpDebugPrintf.
+ //
+ // If |ref_inst_itr| is an OpDebugPrintf, return in |new_blocks| the result
+ // of replacing it with buffer write instructions within its block at
+ // |ref_block_itr|. The instructions write a record to the printf
+ // output buffer stream including |function_idx, instruction_idx, stage_idx|
+ // and removes the OpDebugPrintf. The block at |ref_block_itr| can just be
+ // replaced with the block in |new_blocks|. Besides the buffer writes, this
+ // block will comprise all instructions preceding and following
+ // |ref_inst_itr|.
+ //
+ // This function is designed to be passed to
+ // InstrumentPass::InstProcessEntryPointCallTree(), which applies the
+ // function to each instruction in a module and replaces the instruction
+ // if warranted.
+ //
+ // This instrumentation function utilizes GenDebugStreamWrite() to write its
+ // error records. The validation-specific part of the error record will
+ // consist of a uint32 which is the id of the format string plus a sequence
+ // of uint32s representing the values of the remaining operands of the
+ // DebugPrintf.
+ void GenDebugPrintfCode(BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr,
+ uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+ // Generate a sequence of uint32 instructions in |builder| (if necessary)
+ // representing the value of |val_inst|, which must be a buffer pointer, a
+ // uint64, or a scalar or vector of type uint32, float32 or float16. Append
+ // the ids of all values to the end of |val_ids|.
+ void GenOutputValues(Instruction* val_inst, std::vector<uint32_t>* val_ids,
+ InstructionBuilder* builder);
+
+ // Generate instructions to write a record containing the operands of
+ // |printf_inst| arguments to printf buffer, adding new code to the end of
+ // the last block in |new_blocks|. Kill OpDebugPrintf instruction.
+ void GenOutputCode(Instruction* printf_inst, uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+ // Initialize state for instrumenting bindless checking
+ void InitializeInstDebugPrintf();
+
+ // Apply GenDebugPrintfCode to every instruction in module.
+ Pass::Status ProcessImpl();
+
+ uint32_t ext_inst_printf_id_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index b1a6edb9..c8c6c211 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -380,6 +380,8 @@ uint32_t InstrumentPass::GetOutputBufferBinding() {
return kDebugOutputBindingStream;
case kInstValidationIdBuffAddr:
return kDebugOutputBindingStream;
+ case kInstValidationIdDebugPrintf:
+ return kDebugOutputPrintfStream;
default:
assert(false && "unexpected validation id");
}
@@ -529,6 +531,16 @@ uint32_t InstrumentPass::GetInputBufferId() {
return input_buffer_id_;
}
+uint32_t InstrumentPass::GetFloatId() {
+ if (float_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);
+ float_id_ = type_mgr->GetTypeInstruction(reg_float_ty);
+ }
+ return float_id_;
+}
+
uint32_t InstrumentPass::GetVec4FloatId() {
if (v4float_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -561,6 +573,16 @@ uint32_t InstrumentPass::GetUint64Id() {
return uint64_id_;
}
+uint32_t InstrumentPass::GetUint8Id() {
+ if (uint8_id_ == 0) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Integer uint8_ty(8, false);
+ analysis::Type* reg_uint8_ty = type_mgr->GetRegisteredType(&uint8_ty);
+ uint8_id_ = type_mgr->GetTypeInstruction(reg_uint8_ty);
+ }
+ return uint8_id_;
+}
+
uint32_t InstrumentPass::GetVecUintId(uint32_t len) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Integer uint_ty(32, false);
@@ -606,21 +628,22 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx,
// Total param count is common params plus validation-specific
// params
uint32_t param_cnt = kInstCommonParamCnt + val_spec_param_cnt;
- if (output_func_id_ == 0) {
+ if (param2output_func_id_[param_cnt] == 0) {
// Create function
- output_func_id_ = TakeNextId();
+ param2output_func_id_[param_cnt] = 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)}}}));
+ std::unique_ptr<Instruction> func_inst(
+ new Instruction(get_module()->context(), SpvOpFunction, GetVoidId(),
+ param2output_func_id_[param_cnt],
+ {{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));
@@ -709,10 +732,8 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx,
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_;
+ return param2output_func_id_[param_cnt];
}
uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
@@ -848,7 +869,7 @@ bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
std::unordered_set<uint32_t> done;
// Don't process input and output functions
for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
- if (output_func_id_ != 0) done.insert(output_func_id_);
+ for (auto& ofn : param2output_func_id_) done.insert(ofn.second);
// Process all functions from roots
while (!roots->empty()) {
const uint32_t fi = roots->front();
@@ -926,12 +947,12 @@ void InstrumentPass::InitializeInstrument() {
output_buffer_id_ = 0;
output_buffer_ptr_id_ = 0;
input_buffer_ptr_id_ = 0;
- output_func_id_ = 0;
- output_func_param_cnt_ = 0;
input_buffer_id_ = 0;
+ float_id_ = 0;
v4float_id_ = 0;
uint_id_ = 0;
uint64_id_ = 0;
+ uint8_id_ = 0;
v4uint_id_ = 0;
v3uint_id_ = 0;
bool_id_ = 0;
@@ -944,6 +965,10 @@ void InstrumentPass::InitializeInstrument() {
id2function_.clear();
id2block_.clear();
+ // clear maps
+ param2input_func_id_.clear();
+ param2output_func_id_.clear();
+
// Initialize function and block maps.
for (auto& fn : *get_module()) {
id2function_[fn.result_id()] = &fn;
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index 02568fb7..11afdce8 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -61,6 +61,7 @@ namespace opt {
// its output buffers.
static const uint32_t kInstValidationIdBindless = 0;
static const uint32_t kInstValidationIdBuffAddr = 1;
+static const uint32_t kInstValidationIdDebugPrintf = 2;
class InstrumentPass : public Pass {
using cbb_ptr = const BasicBlock*;
@@ -227,9 +228,12 @@ class InstrumentPass : public Pass {
// Return id for 32-bit unsigned type
uint32_t GetUintId();
- // Return id for 32-bit unsigned type
+ // Return id for 64-bit unsigned type
uint32_t GetUint64Id();
+ // Return id for 8-bit unsigned type
+ uint32_t GetUint8Id();
+
// Return id for 32-bit unsigned type
uint32_t GetBoolId();
@@ -267,6 +271,9 @@ class InstrumentPass : public Pass {
// Return id for debug input buffer
uint32_t GetInputBufferId();
+ // Return id for 32-bit float type
+ uint32_t GetFloatId();
+
// Return id for v4float type
uint32_t GetVec4FloatId();
@@ -383,17 +390,17 @@ class InstrumentPass : public Pass {
uint32_t input_buffer_ptr_id_;
// id for debug output function
- uint32_t output_func_id_;
+ std::unordered_map<uint32_t, uint32_t> param2output_func_id_;
// ids for debug input functions
std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
- // param count for output function
- uint32_t output_func_param_cnt_;
-
// id for input buffer variable
uint32_t input_buffer_id_;
+ // id for 32-bit float type
+ uint32_t float_id_;
+
// id for v4float type
uint32_t v4float_id_;
@@ -406,9 +413,12 @@ class InstrumentPass : public Pass {
// id for 32-bit unsigned type
uint32_t uint_id_;
- // id for 32-bit unsigned type
+ // id for 64-bit unsigned type
uint32_t uint64_id_;
+ // id for 8-bit unsigned type
+ uint32_t uint8_id_;
+
// id for bool type
uint32_t bool_id_;
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 241aa75b..6e271f53 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -425,6 +425,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
RegisterPass(CreateConvertRelaxedToHalfPass());
} else if (pass_name == "relax-float-ops") {
RegisterPass(CreateRelaxFloatOpsPass());
+ } else if (pass_name == "inst-debug-printf") {
+ RegisterPass(CreateInstDebugPrintfPass(7, 23));
} else if (pass_name == "simplify-instructions") {
RegisterPass(CreateSimplificationPass());
} else if (pass_name == "ssa-rewrite") {
@@ -886,6 +888,12 @@ Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
input_init_enable, version));
}
+Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
+ uint32_t shader_id) {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::InstDebugPrintfPass>(desc_set, shader_id));
+}
+
Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t desc_set,
uint32_t shader_id,
uint32_t version) {
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 1a3675c7..5b4ab898 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -46,6 +46,7 @@
#include "source/opt/inline_opaque_pass.h"
#include "source/opt/inst_bindless_check_pass.h"
#include "source/opt/inst_buff_addr_check_pass.h"
+#include "source/opt/inst_debug_printf_pass.h"
#include "source/opt/legalize_vector_shuffle_pass.h"
#include "source/opt/licm_pass.h"
#include "source/opt/local_access_chain_convert_pass.h"
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 327f2656..39543389 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -55,6 +55,7 @@ add_spvtools_unittest(TARGET opt
insert_extract_elim_test.cpp
inst_bindless_check_test.cpp
inst_buff_addr_check_test.cpp
+ inst_debug_printf_test.cpp
instruction_list_test.cpp
instruction_test.cpp
ir_builder.cpp
diff --git a/test/opt/inst_debug_printf_test.cpp b/test/opt/inst_debug_printf_test.cpp
new file mode 100644
index 00000000..8123ffbb
--- /dev/null
+++ b/test/opt/inst_debug_printf_test.cpp
@@ -0,0 +1,215 @@
+// Copyright (c) 2020 Valve Corporation
+// Copyright (c) 2020 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.
+
+// Debug Printf Instrumentation Tests.
+
+#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 InstDebugPrintfTest = PassTest<::testing::Test>;
+
+TEST_F(InstDebugPrintfTest, V4Float32) {
+ // SamplerState g_sDefault;
+ // Texture2D g_tColor;
+ //
+ // struct PS_INPUT
+ // {
+ // float2 vBaseTexCoord : TEXCOORD0;
+ // };
+ //
+ // struct PS_OUTPUT
+ // {
+ // float4 vDiffuse : SV_Target0;
+ // };
+ //
+ // PS_OUTPUT MainPs(PS_INPUT i)
+ // {
+ // PS_OUTPUT o;
+ //
+ // o.vDiffuse.rgba = g_tColor.Sample(g_sDefault, (i.vBaseTexCoord.xy).xy);
+ // debugPrintfEXT("diffuse: %v4f", o.vDiffuse.rgba);
+ // return o;
+ // }
+
+ const std::string defs =
+ R"(OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.DebugPrintf"
+; CHECK-NOT: OpExtension "SPV_KHR_non_semantic_info"
+; CHECK-NOT: %1 = OpExtInstImport "NonSemantic.DebugPrintf"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "MainPs" %3 %4
+; CHECK: OpEntryPoint Fragment %2 "MainPs" %3 %4 %gl_FragCoord
+OpExecutionMode %2 OriginUpperLeft
+%5 = OpString "Color is %vn"
+)";
+
+ const std::string decorates =
+ R"(OpDecorate %6 DescriptorSet 0
+OpDecorate %6 Binding 1
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+OpDecorate %3 Location 0
+OpDecorate %4 Location 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+; CHECK: OpDecorate %_struct_47 Block
+; CHECK: OpMemberDecorate %_struct_47 0 Offset 0
+; CHECK: OpMemberDecorate %_struct_47 1 Offset 4
+; CHECK: OpDecorate %49 DescriptorSet 7
+; CHECK: OpDecorate %49 Binding 3
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
+)";
+
+ const std::string globals =
+ R"(%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%13 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%6 = OpVariable %_ptr_UniformConstant_13 UniformConstant
+%15 = OpTypeSampler
+%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
+%7 = OpVariable %_ptr_UniformConstant_15 UniformConstant
+%17 = OpTypeSampledImage %13
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%3 = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%4 = OpVariable %_ptr_Output_v4float Output
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK: %38 = OpTypeFunction %void %uint %uint %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+; CHECK: %_struct_47 = OpTypeStruct %uint %_runtimearr_uint
+; CHECK: %_ptr_StorageBuffer__struct_47 = OpTypePointer StorageBuffer %_struct_47
+; CHECK: %49 = OpVariable %_ptr_StorageBuffer__struct_47 StorageBuffer
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+)";
+
+ const std::string main =
+ R"(%2 = OpFunction %void None %9
+%20 = OpLabel
+%21 = OpLoad %v2float %3
+%22 = OpLoad %13 %6
+%23 = OpLoad %15 %7
+%24 = OpSampledImage %17 %22 %23
+%25 = OpImageSampleImplicitLod %v4float %24 %21
+%26 = OpExtInst %void %1 1 %5 %25
+; CHECK-NOT: %26 = OpExtInst %void %1 1 %5 %25
+; CHECK: %29 = OpCompositeExtract %float %25 0
+; CHECK: %30 = OpBitcast %uint %29
+; CHECK: %31 = OpCompositeExtract %float %25 1
+; CHECK: %32 = OpBitcast %uint %31
+; CHECK: %33 = OpCompositeExtract %float %25 2
+; CHECK: %34 = OpBitcast %uint %33
+; CHECK: %35 = OpCompositeExtract %float %25 3
+; CHECK: %36 = OpBitcast %uint %35
+; CHECK: %101 = OpFunctionCall %void %37 %uint_36 %uint_5 %30 %32 %34 %36
+; CHECK: OpBranch %102
+; CHECK: %102 = OpLabel
+OpStore %4 %25
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(; CHECK: %37 = OpFunction %void None %38
+; CHECK: %39 = OpFunctionParameter %uint
+; CHECK: %40 = OpFunctionParameter %uint
+; CHECK: %41 = OpFunctionParameter %uint
+; CHECK: %42 = OpFunctionParameter %uint
+; CHECK: %43 = OpFunctionParameter %uint
+; CHECK: %44 = OpFunctionParameter %uint
+; CHECK: %45 = OpLabel
+; CHECK: %52 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_0
+; CHECK: %55 = OpAtomicIAdd %uint %52 %uint_4 %uint_0 %uint_12
+; CHECK: %56 = OpIAdd %uint %55 %uint_12
+; CHECK: %57 = OpArrayLength %uint %49 1
+; CHECK: %59 = OpULessThanEqual %bool %56 %57
+; CHECK: OpSelectionMerge %60 None
+; CHECK: OpBranchConditional %59 %61 %60
+; CHECK: %61 = OpLabel
+; CHECK: %62 = OpIAdd %uint %55 %uint_0
+; CHECK: %64 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %62
+; CHECK: OpStore %64 %uint_12
+; CHECK: %66 = OpIAdd %uint %55 %uint_1
+; CHECK: %67 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %66
+; CHECK: OpStore %67 %uint_23
+; CHECK: %69 = OpIAdd %uint %55 %uint_2
+; CHECK: %70 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %69
+; CHECK: OpStore %70 %39
+; CHECK: %72 = OpIAdd %uint %55 %uint_3
+; CHECK: %73 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %72
+; CHECK: OpStore %73 %uint_4
+; CHECK: %76 = OpLoad %v4float %gl_FragCoord
+; CHECK: %78 = OpBitcast %v4uint %76
+; CHECK: %79 = OpCompositeExtract %uint %78 0
+; CHECK: %80 = OpIAdd %uint %55 %uint_4
+; CHECK: %81 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %80
+; CHECK: OpStore %81 %79
+; CHECK: %82 = OpCompositeExtract %uint %78 1
+; CHECK: %83 = OpIAdd %uint %55 %uint_5
+; CHECK: %84 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %83
+; CHECK: OpStore %84 %82
+; CHECK: %86 = OpIAdd %uint %55 %uint_7
+; CHECK: %87 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %86
+; CHECK: OpStore %87 %40
+; CHECK: %89 = OpIAdd %uint %55 %uint_8
+; CHECK: %90 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %89
+; CHECK: OpStore %90 %41
+; CHECK: %92 = OpIAdd %uint %55 %uint_9
+; CHECK: %93 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %92
+; CHECK: OpStore %93 %42
+; CHECK: %95 = OpIAdd %uint %55 %uint_10
+; CHECK: %96 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %95
+; CHECK: OpStore %96 %43
+; CHECK: %98 = OpIAdd %uint %55 %uint_11
+; CHECK: %99 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %98
+; CHECK: OpStore %99 %44
+; CHECK: OpBranch %60
+; CHECK: %60 = OpLabel
+; CHECK: OpReturn
+; CHECK: OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<InstDebugPrintfPass>(
+ defs + decorates + globals + main + output_func, true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// Compute shader
+// Geometry shader
+// Tesselation control shader
+// Tesselation eval shader
+// Vertex shader
+
+} // namespace
+} // namespace opt
+} // namespace spvtools