diff --git a/Android.mk b/Android.mk index 04d21c557..abbd42b23 100644 --- a/Android.mk +++ b/Android.mk @@ -129,10 +129,8 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/inline_pass.cpp \ source/opt/inline_exhaustive_pass.cpp \ source/opt/inline_opaque_pass.cpp \ - source/opt/inst_debug_printf_pass.cpp \ source/opt/instruction.cpp \ source/opt/instruction_list.cpp \ - source/opt/instrument_pass.cpp \ source/opt/interface_var_sroa.cpp \ source/opt/interp_fixup_pass.cpp \ source/opt/invocation_interlock_placement_pass.cpp \ diff --git a/BUILD.bazel b/BUILD.bazel index ee16c0a3b..7dc73d081 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -178,7 +178,6 @@ cc_library( cc_library( name = "spirv_tools_opt", hdrs = [ - "include/spirv-tools/instrument.hpp", "include/spirv-tools/optimizer.hpp", ], copts = COMMON_COPTS, @@ -196,7 +195,6 @@ cc_library( ":gen_vendor_tables_spv_amd_shader_ballot", ], hdrs = glob(["source/opt/*.h"]) + [ - "include/spirv-tools/instrument.hpp", "include/spirv-tools/optimizer.hpp", ], copts = COMMON_COPTS, diff --git a/BUILD.gn b/BUILD.gn index a6e885806..59a34d6f9 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -388,7 +388,6 @@ config("spvtools_internal_config") { source_set("spvtools_headers") { sources = [ - "include/spirv-tools/instrument.hpp", "include/spirv-tools/libspirv.h", "include/spirv-tools/libspirv.hpp", "include/spirv-tools/linker.hpp", @@ -683,14 +682,10 @@ static_library("spvtools_opt") { "source/opt/inline_opaque_pass.h", "source/opt/inline_pass.cpp", "source/opt/inline_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", "source/opt/instruction_list.h", - "source/opt/instrument_pass.cpp", - "source/opt/instrument_pass.h", "source/opt/interface_var_sroa.cpp", "source/opt/interface_var_sroa.h", "source/opt/interp_fixup_pass.cpp", diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ba173f1d..19d710761 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,7 +371,6 @@ if(ENABLE_SPIRV_TOOLS_INSTALL) ${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 deleted file mode 100644 index d72a5d876..000000000 --- a/include/spirv-tools/instrument.hpp +++ /dev/null @@ -1,79 +0,0 @@ -// 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: -// -// CreateInstDebugPrintfPass -// -// More detailed documentation of these routines can be found in optimizer.hpp - -namespace spvtools { - -// Stream Output Buffer Offsets -// -// The following values provide offsets into the output buffer struct -// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized -// by InstDebugPrintfPass. -// -// The 1st member of the debug output buffer contains a set of flags -// controlling the behavior of instrumentation code. -static const int kDebugOutputFlagsOffset = 0; - -// The 2nd member 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 = 1; - -// The 3rd member 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 = 2; - -// 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; - -// 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 InstDebugPrintfPass. -static const int kDebugOutputPrintfStream = 3; - -} // namespace spvtools - -#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_ diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index 9677b15d1..9427b01eb 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -747,18 +747,6 @@ Optimizer::PassToken CreateReduceLoadSizePass( // them into a single instruction where possible. Optimizer::PassToken CreateCombineAccessChainsPass(); -// 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/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 5e6a5193e..4a12e6ef5 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -64,10 +64,8 @@ set(SPIRV_TOOLS_OPT_SOURCES inline_exhaustive_pass.h inline_opaque_pass.h inline_pass.h - inst_debug_printf_pass.h instruction.h instruction_list.h - instrument_pass.h interface_var_sroa.h invocation_interlock_placement_pass.h interp_fixup_pass.h @@ -185,10 +183,8 @@ set(SPIRV_TOOLS_OPT_SOURCES inline_exhaustive_pass.cpp inline_opaque_pass.cpp inline_pass.cpp - inst_debug_printf_pass.cpp instruction.cpp instruction_list.cpp - instrument_pass.cpp interface_var_sroa.cpp invocation_interlock_placement_pass.cpp interp_fixup_pass.cpp diff --git a/source/opt/inst_debug_printf_pass.cpp b/source/opt/inst_debug_printf_pass.cpp deleted file mode 100644 index 916db7ce2..000000000 --- a/source/opt/inst_debug_printf_pass.cpp +++ /dev/null @@ -1,484 +0,0 @@ -// 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 "source/spirv_constant.h" -#include "source/to_string.h" -#include "source/util/string_utils.h" -#include "spirv/unified1/NonSemanticDebugPrintf.h" - -namespace spvtools { -namespace opt { - -void InstDebugPrintfPass::GenOutputValues(Instruction* val_inst, - std::vector* 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->AddCompositeExtract(c_ty_id, 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->AddSelect( - GetUintId(), 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(), spv::Op::OpFConvert, 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(), spv::Op::OpBitcast, val_inst->result_id()); - GenOutputValues(ui64_inst, val_ids, builder); - return; - } - case 32: { - // Bitcase float32 to uint32 - Instruction* bc_inst = builder->AddUnaryOp( - GetUintId(), spv::Op::OpBitcast, 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(), spv::Op::OpBitcast, - val_inst->result_id()); - } - // Break uint64 into 2x uint32 - Instruction* lo_ui64_inst = builder->AddUnaryOp( - GetUintId(), spv::Op::OpUConvert, ui64_inst->result_id()); - Instruction* rshift_ui64_inst = builder->AddBinaryOp( - GetUint64Id(), spv::Op::OpShiftRightLogical, - ui64_inst->result_id(), builder->GetUintConstantId(32)); - Instruction* hi_ui64_inst = builder->AddUnaryOp( - GetUintId(), spv::Op::OpUConvert, 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(), spv::Op::OpBitcast, - val_inst->result_id()); - } - // Convert uint8 to uint32 - Instruction* ui32_inst = builder->AddUnaryOp( - GetUintId(), spv::Op::OpUConvert, 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(), spv::Op::OpBitcast, - 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, - std::vector>* 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 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() == spv::Op::OpString) { - uint32_t string_id_id = builder.GetUintConstantId(*iid); - val_ids.push_back(string_id_id); - } else { - GenOutputValues(opnd_inst, &val_ids, &builder); - } - }); - GenDebugStreamWrite( - builder.GetUintConstantId(shader_id_), - builder.GetUintConstantId(uid2offset_[printf_inst->unique_id()]), val_ids, - &builder); - context()->KillInst(printf_inst); -} - -void InstDebugPrintfPass::GenDebugPrintfCode( - BasicBlock::iterator ref_inst_itr, - UptrVectorIterator ref_block_itr, - std::vector>* new_blocks) { - // If not DebugPrintf OpExtInst, return. - Instruction* printf_inst = &*ref_inst_itr; - if (printf_inst->opcode() != spv::Op::OpExtInst) 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 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, 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 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)); -} - -// Return id for output buffer -uint32_t InstDebugPrintfPass::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::RuntimeArray* reg_uint_rarr_ty = GetUintRuntimeArrayType(32); - analysis::Integer* reg_uint_ty = GetInteger(32, false); - analysis::Type* reg_buf_ty = - GetStruct({reg_uint_ty, reg_uint_ty, reg_uint_rarr_ty}); - uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty); - // By the Vulkan spec, a pre-existing struct containing a RuntimeArray - // must be a block, and will therefore be decorated with Block. Therefore - // the undecorated type returned here will not be pre-existing and can - // safely be decorated. Since this type is now decorated, it is out of - // sync with the TypeManager and therefore the TypeManager must be - // invalidated after this pass. - assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 && - "used struct type returned"); - deco_mgr->AddDecoration(obufTyId, uint32_t(spv::Decoration::Block)); - deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputFlagsOffset, - uint32_t(spv::Decoration::Offset), 0); - deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset, - uint32_t(spv::Decoration::Offset), 4); - deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputDataOffset, - uint32_t(spv::Decoration::Offset), 8); - uint32_t obufTyPtrId_ = - type_mgr->FindPointerToType(obufTyId, spv::StorageClass::StorageBuffer); - output_buffer_id_ = TakeNextId(); - std::unique_ptr newVarOp(new Instruction( - context(), spv::Op::OpVariable, obufTyPtrId_, output_buffer_id_, - {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, - {uint32_t(spv::StorageClass::StorageBuffer)}}})); - context()->AddGlobalValue(std::move(newVarOp)); - context()->AddDebug2Inst(NewGlobalName(obufTyId, "OutputBuffer")); - context()->AddDebug2Inst(NewMemberName(obufTyId, 0, "flags")); - context()->AddDebug2Inst(NewMemberName(obufTyId, 1, "written_count")); - context()->AddDebug2Inst(NewMemberName(obufTyId, 2, "data")); - context()->AddDebug2Inst(NewGlobalName(output_buffer_id_, "output_buffer")); - deco_mgr->AddDecorationVal( - output_buffer_id_, uint32_t(spv::Decoration::DescriptorSet), desc_set_); - deco_mgr->AddDecorationVal(output_buffer_id_, - uint32_t(spv::Decoration::Binding), - GetOutputBufferBinding()); - AddStorageBufferExt(); - if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) { - // Add the new buffer to all entry points. - for (auto& entry : get_module()->entry_points()) { - entry.AddOperand({SPV_OPERAND_TYPE_ID, {output_buffer_id_}}); - context()->AnalyzeUses(&entry); - } - } - } - return output_buffer_id_; -} - -uint32_t InstDebugPrintfPass::GetOutputBufferPtrId() { - if (output_buffer_ptr_id_ == 0) { - output_buffer_ptr_id_ = context()->get_type_mgr()->FindPointerToType( - GetUintId(), spv::StorageClass::StorageBuffer); - } - return output_buffer_ptr_id_; -} - -uint32_t InstDebugPrintfPass::GetOutputBufferBinding() { - return kDebugOutputPrintfStream; -} - -void InstDebugPrintfPass::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->AddIAdd( - GetUintId(), base_offset_id, builder->GetUintConstantId(field_offset)); - uint32_t buf_id = GetOutputBufferId(); - uint32_t buf_uint_ptr_id = GetOutputBufferPtrId(); - Instruction* achain_inst = builder->AddAccessChain( - buf_uint_ptr_id, buf_id, - {builder->GetUintConstantId(kDebugOutputDataOffset), - data_idx_inst->result_id()}); - (void)builder->AddStore(achain_inst->result_id(), val_id); -} - -uint32_t InstDebugPrintfPass::GetStreamWriteFunctionId(uint32_t param_cnt) { - enum { - kShaderId = 0, - kInstructionIndex = 1, - kFirstParam = 2, - }; - // Total param count is common params plus validation-specific - // params - if (param2output_func_id_[param_cnt] == 0) { - // Create function - param2output_func_id_[param_cnt] = TakeNextId(); - analysis::TypeManager* type_mgr = context()->get_type_mgr(); - - const analysis::Type* uint_type = GetInteger(32, false); - - std::vector param_types(kFirstParam + param_cnt, - uint_type); - std::unique_ptr output_func = StartFunction( - param2output_func_id_[param_cnt], type_mgr->GetVoidType(), param_types); - - std::vector param_ids = AddParameters(*output_func, param_types); - - // Create first block - auto new_blk_ptr = MakeUnique(NewLabel(TakeNextId())); - - InstructionBuilder builder( - context(), &*new_blk_ptr, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - // Gen test if debug output buffer size will not be exceeded. - const uint32_t first_param_offset = kInstCommonOutInstructionIdx + 1; - const uint32_t obuf_record_sz = first_param_offset + param_cnt; - const uint32_t buf_id = GetOutputBufferId(); - const uint32_t buf_uint_ptr_id = GetOutputBufferPtrId(); - Instruction* obuf_curr_sz_ac_inst = builder.AddAccessChain( - buf_uint_ptr_id, 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(uint32_t(spv::MemoryAccessMask::MaskNone)); - uint32_t scope_invok_id = - builder.GetUintConstantId(uint32_t(spv::Scope::Invocation)); - Instruction* obuf_curr_sz_inst = builder.AddQuadOp( - GetUintId(), spv::Op::OpAtomicIAdd, 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.AddIAdd(GetUintId(), obuf_curr_sz_id, - builder.GetUintConstantId(obuf_record_sz)); - // Fetch the data bound - Instruction* obuf_bnd_inst = - builder.AddIdLiteralOp(GetUintId(), spv::Op::OpArrayLength, - GetOutputBufferId(), kDebugOutputDataOffset); - // Test that new written size is less than or equal to debug output - // data bound - Instruction* obuf_safe_inst = builder.AddBinaryOp( - GetBoolId(), spv::Op::OpULessThanEqual, 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 merge_label(NewLabel(merge_blk_id)); - std::unique_ptr write_label(NewLabel(write_blk_id)); - (void)builder.AddConditionalBranch( - obuf_safe_inst->result_id(), write_blk_id, merge_blk_id, merge_blk_id, - uint32_t(spv::SelectionControlMask::MaskNone)); - // Close safety test block and gen write block - output_func->AddBasicBlock(std::move(new_blk_ptr)); - new_blk_ptr = MakeUnique(std::move(write_label)); - builder.SetInsertPoint(&*new_blk_ptr); - // Generate common and stage-specific debug record members - GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutSize, - builder.GetUintConstantId(obuf_record_sz), - &builder); - // Store Shader Id - GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutShaderId, - param_ids[kShaderId], &builder); - // Store Instruction Idx - GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutInstructionIdx, - param_ids[kInstructionIndex], &builder); - // Gen writes of validation specific data - for (uint32_t i = 0; i < param_cnt; ++i) { - GenDebugOutputFieldCode(obuf_curr_sz_id, first_param_offset + i, - param_ids[kFirstParam + i], &builder); - } - // Close write block and gen merge block - (void)builder.AddBranch(merge_blk_id); - output_func->AddBasicBlock(std::move(new_blk_ptr)); - new_blk_ptr = MakeUnique(std::move(merge_label)); - builder.SetInsertPoint(&*new_blk_ptr); - // Close merge block and function and add function to module - (void)builder.AddNullaryOp(0, spv::Op::OpReturn); - - output_func->AddBasicBlock(std::move(new_blk_ptr)); - output_func->SetFunctionEnd(EndFunction()); - context()->AddFunction(std::move(output_func)); - - std::string name("stream_write_"); - name += spvtools::to_string(param_cnt); - - context()->AddDebug2Inst( - NewGlobalName(param2output_func_id_[param_cnt], name)); - } - return param2output_func_id_[param_cnt]; -} - -void InstDebugPrintfPass::GenDebugStreamWrite( - uint32_t shader_id, uint32_t instruction_idx_id, - const std::vector& 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(validation_ids.size()); - std::vector args = {shader_id, instruction_idx_id}; - (void)args.insert(args.end(), validation_ids.begin(), validation_ids.end()); - (void)builder->AddFunctionCall(GetVoidId(), - GetStreamWriteFunctionId(val_id_cnt), args); -} - -std::unique_ptr InstDebugPrintfPass::NewGlobalName( - uint32_t id, const std::string& name_str) { - std::string prefixed_name{"inst_printf_"}; - prefixed_name += name_str; - return NewName(id, prefixed_name); -} - -std::unique_ptr InstDebugPrintfPass::NewMemberName( - uint32_t id, uint32_t member_index, const std::string& name_str) { - return MakeUnique( - context(), spv::Op::OpMemberName, 0, 0, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {id}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index}}, - {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}}); -} - -void InstDebugPrintfPass::InitializeInstDebugPrintf() { - // Initialize base class - InitializeInstrument(); - output_buffer_id_ = 0; - output_buffer_ptr_id_ = 0; -} - -Pass::Status InstDebugPrintfPass::ProcessImpl() { - // Perform printf instrumentation on each entry point function in module - InstProcessFunction pfn = - [this](BasicBlock::iterator ref_inst_itr, - UptrVectorIterator ref_block_itr, - [[maybe_unused]] uint32_t stage_idx, - std::vector>* new_blocks) { - return GenDebugPrintfCode(ref_inst_itr, ref_block_itr, 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 std::string set_name = c_itr->GetInOperand(0).AsString(); - if (spvtools::utils::starts_with(set_name, "NonSemantic.")) { - non_sem_set_seen = true; - break; - } - } - if (!non_sem_set_seen) { - context()->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 deleted file mode 100644 index 5688d3841..000000000 --- a/source/opt/inst_debug_printf_pass.h +++ /dev/null @@ -1,198 +0,0 @@ -// 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, false, false) {} - // For all other interfaces - InstDebugPrintfPass(uint32_t desc_set, uint32_t shader_id) - : InstrumentPass(desc_set, shader_id, false, false) {} - - ~InstDebugPrintfPass() override = default; - - // See optimizer.hpp for pass user documentation. - Status Process() override; - - const char* name() const override { return "inst-printf-pass"; } - - private: - // 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 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. 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 - // ... - // Validation Error Code - // Validation-specific Word 0 - // Validation-specific Word 1 - // Validation-specific Word 2 - // ... - // - // Each record consists of two subsections: members common across all - // validation 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 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 shader_id, uint32_t instruction_idx_id, - const std::vector& validation_ids, - InstructionBuilder* builder); - - // Return id for output function. Define if it doesn't exist with - // |val_spec_param_cnt| validation-specific uint32 parameters. - uint32_t GetStreamWriteFunctionId(uint32_t val_spec_param_cnt); - - // 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| - // 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 ref_block_itr, - std::vector>* 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* 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, - std::vector>* new_blocks); - - // Set the name for a function or global variable, names will be - // prefixed to identify which instrumentation pass generated them. - std::unique_ptr NewGlobalName(uint32_t id, - const std::string& name_str); - - // Set the name for a structure member - std::unique_ptr NewMemberName(uint32_t id, uint32_t member_index, - const std::string& name_str); - - // Return id for debug output buffer - uint32_t GetOutputBufferId(); - - // Return id for buffer uint type - uint32_t GetOutputBufferPtrId(); - - // Return binding for output buffer for current validation. - uint32_t GetOutputBufferBinding(); - - // Initialize state for instrumenting bindless checking - void InitializeInstDebugPrintf(); - - // Apply GenDebugPrintfCode to every instruction in module. - Pass::Status ProcessImpl(); - - uint32_t ext_inst_printf_id_{0}; - - // id for output buffer variable - uint32_t output_buffer_id_{0}; - - // ptr type id for output buffer element - uint32_t output_buffer_ptr_id_{0}; -}; - -} // 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 deleted file mode 100644 index b6845a599..000000000 --- a/source/opt/instrument_pass.cpp +++ /dev/null @@ -1,803 +0,0 @@ -// 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" -#include "source/spirv_constant.h" - -namespace spvtools { -namespace opt { -namespace { -// Indices of operands in SPIR-V instructions -constexpr int kEntryPointFunctionIdInIdx = 1; -} // namespace - -void InstrumentPass::MovePreludeCode( - BasicBlock::iterator ref_inst_itr, - UptrVectorIterator ref_block_itr, - std::unique_ptr* 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 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 ref_block_itr, BasicBlock* new_blk_ptr) { - // 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 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 InstrumentPass::NewLabel(uint32_t label_id) { - auto new_label = - MakeUnique(context(), spv::Op::OpLabel, 0, label_id, - std::initializer_list{}); - get_def_use_mgr()->AnalyzeInstDefUse(&*new_label); - return new_label; -} - -std::unique_ptr InstrumentPass::StartFunction( - uint32_t func_id, const analysis::Type* return_type, - const std::vector& param_types) { - analysis::TypeManager* type_mgr = context()->get_type_mgr(); - analysis::Function* func_type = GetFunction(return_type, param_types); - - const std::vector operands{ - {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, - {uint32_t(spv::FunctionControlMask::MaskNone)}}, - {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_mgr->GetId(func_type)}}, - }; - auto func_inst = - MakeUnique(context(), spv::Op::OpFunction, - type_mgr->GetId(return_type), func_id, operands); - get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst); - return MakeUnique(std::move(func_inst)); -} - -std::unique_ptr InstrumentPass::EndFunction() { - auto end = MakeUnique(context(), spv::Op::OpFunctionEnd, 0, 0, - std::initializer_list{}); - get_def_use_mgr()->AnalyzeInstDefUse(end.get()); - return end; -} - -std::vector InstrumentPass::AddParameters( - Function& func, const std::vector& param_types) { - std::vector param_ids; - param_ids.reserve(param_types.size()); - for (const analysis::Type* param : param_types) { - uint32_t pid = TakeNextId(); - param_ids.push_back(pid); - auto param_inst = - MakeUnique(context(), spv::Op::OpFunctionParameter, - context()->get_type_mgr()->GetId(param), pid, - std::initializer_list{}); - get_def_use_mgr()->AnalyzeInstDefUse(param_inst.get()); - func.AddParameter(std::move(param_inst)); - } - return param_ids; -} - -std::unique_ptr InstrumentPass::NewName( - uint32_t id, const std::string& name_str) { - return MakeUnique( - context(), spv::Op::OpName, 0, 0, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {id}}, - {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}}); -} - -uint32_t InstrumentPass::Gen32BitCvtCode(uint32_t val_id, - InstructionBuilder* builder) { - // Convert integer value to 32-bit if necessary - analysis::TypeManager* type_mgr = context()->get_type_mgr(); - uint32_t val_ty_id = get_def_use_mgr()->GetDef(val_id)->type_id(); - analysis::Integer* val_ty = type_mgr->GetType(val_ty_id)->AsInteger(); - if (val_ty->width() == 32) return val_id; - bool is_signed = val_ty->IsSigned(); - analysis::Integer val_32b_ty(32, is_signed); - analysis::Type* val_32b_reg_ty = type_mgr->GetRegisteredType(&val_32b_ty); - uint32_t val_32b_reg_ty_id = type_mgr->GetId(val_32b_reg_ty); - if (is_signed) - return builder->AddUnaryOp(val_32b_reg_ty_id, spv::Op::OpSConvert, val_id) - ->result_id(); - else - return builder->AddUnaryOp(val_32b_reg_ty_id, spv::Op::OpUConvert, val_id) - ->result_id(); -} - -uint32_t InstrumentPass::GenUintCastCode(uint32_t val_id, - InstructionBuilder* builder) { - // Convert value to 32-bit if necessary - uint32_t val_32b_id = Gen32BitCvtCode(val_id, builder); - // Cast value to unsigned if necessary - analysis::TypeManager* type_mgr = context()->get_type_mgr(); - uint32_t val_ty_id = get_def_use_mgr()->GetDef(val_32b_id)->type_id(); - analysis::Integer* val_ty = type_mgr->GetType(val_ty_id)->AsInteger(); - if (!val_ty->IsSigned()) return val_32b_id; - return builder->AddUnaryOp(GetUintId(), spv::Op::OpBitcast, val_32b_id) - ->result_id(); -} - -uint32_t InstrumentPass::GenVarLoad(uint32_t var_id, - InstructionBuilder* builder) { - Instruction* var_inst = get_def_use_mgr()->GetDef(var_id); - uint32_t type_id = GetPointeeTypeId(var_inst); - Instruction* load_inst = builder->AddLoad(type_id, var_id); - return load_inst->result_id(); -} - -uint32_t InstrumentPass::GenStageInfo(uint32_t stage_idx, - InstructionBuilder* builder) { - std::vector ids(4, builder->GetUintConstantId(0)); - ids[0] = builder->GetUintConstantId(stage_idx); - // %289 = OpCompositeConstruct %v4uint %uint_0 %285 %288 %uint_0 - // TODO(greg-lunarg): Add support for all stages - switch (spv::ExecutionModel(stage_idx)) { - case spv::ExecutionModel::Vertex: { - // Load and store VertexId and InstanceId - uint32_t load_id = GenVarLoad( - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::VertexIndex)), - builder); - ids[1] = GenUintCastCode(load_id, builder); - - load_id = GenVarLoad(context()->GetBuiltinInputVarId( - uint32_t(spv::BuiltIn::InstanceIndex)), - builder); - ids[2] = GenUintCastCode(load_id, builder); - } break; - case spv::ExecutionModel::GLCompute: - case spv::ExecutionModel::TaskNV: - case spv::ExecutionModel::MeshNV: - case spv::ExecutionModel::TaskEXT: - case spv::ExecutionModel::MeshEXT: { - // Load and store GlobalInvocationId. - uint32_t load_id = GenVarLoad(context()->GetBuiltinInputVarId(uint32_t( - spv::BuiltIn::GlobalInvocationId)), - builder); - for (uint32_t u = 0; u < 3u; ++u) { - ids[u + 1] = builder->AddCompositeExtract(GetUintId(), load_id, {u}) - ->result_id(); - } - } break; - case spv::ExecutionModel::Geometry: { - // Load and store PrimitiveId and InvocationId. - uint32_t load_id = GenVarLoad( - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)), - builder); - ids[1] = load_id; - load_id = GenVarLoad( - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::InvocationId)), - builder); - ids[2] = GenUintCastCode(load_id, builder); - } break; - case spv::ExecutionModel::TessellationControl: { - // Load and store InvocationId and PrimitiveId - uint32_t load_id = GenVarLoad( - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::InvocationId)), - builder); - ids[1] = GenUintCastCode(load_id, builder); - load_id = GenVarLoad( - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)), - builder); - ids[2] = load_id; - } break; - case spv::ExecutionModel::TessellationEvaluation: { - // Load and store PrimitiveId and TessCoord.uv - uint32_t load_id = GenVarLoad( - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)), - builder); - ids[1] = load_id; - load_id = GenVarLoad( - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::TessCoord)), - builder); - Instruction* uvec3_cast_inst = - builder->AddUnaryOp(GetVec3UintId(), spv::Op::OpBitcast, load_id); - uint32_t uvec3_cast_id = uvec3_cast_inst->result_id(); - for (uint32_t u = 0; u < 2u; ++u) { - ids[u + 2] = - builder->AddCompositeExtract(GetUintId(), uvec3_cast_id, {u}) - ->result_id(); - } - } break; - case spv::ExecutionModel::Fragment: { - // Load FragCoord and convert to Uint - Instruction* frag_coord_inst = builder->AddLoad( - GetVec4FloatId(), - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::FragCoord))); - Instruction* uint_frag_coord_inst = builder->AddUnaryOp( - GetVec4UintId(), spv::Op::OpBitcast, frag_coord_inst->result_id()); - for (uint32_t u = 0; u < 2u; ++u) { - ids[u + 1] = - builder - ->AddCompositeExtract(GetUintId(), - uint_frag_coord_inst->result_id(), {u}) - ->result_id(); - } - } break; - case spv::ExecutionModel::RayGenerationNV: - case spv::ExecutionModel::IntersectionNV: - case spv::ExecutionModel::AnyHitNV: - case spv::ExecutionModel::ClosestHitNV: - case spv::ExecutionModel::MissNV: - case spv::ExecutionModel::CallableNV: { - // Load and store LaunchIdNV. - uint32_t launch_id = GenVarLoad( - context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::LaunchIdNV)), - builder); - for (uint32_t u = 0; u < 3u; ++u) { - ids[u + 1] = builder->AddCompositeExtract(GetUintId(), launch_id, {u}) - ->result_id(); - } - } break; - default: { assert(false && "unsupported stage"); } break; - } - return builder->AddCompositeConstruct(GetVec4UintId(), ids)->result_id(); -} - -bool InstrumentPass::AllConstant(const std::vector& ids) { - for (auto& id : ids) { - Instruction* id_inst = context()->get_def_use_mgr()->GetDef(id); - if (!spvOpcodeIsConstant(id_inst->opcode())) return false; - } - return true; -} - -uint32_t InstrumentPass::GenReadFunctionCall( - uint32_t return_id, uint32_t func_id, - const std::vector& func_call_args, - InstructionBuilder* ref_builder) { - // If optimizing direct reads and the call has already been generated, - // use its result - if (opt_direct_reads_) { - uint32_t res_id = call2id_[func_call_args]; - if (res_id != 0) return res_id; - } - // If the function arguments are all constants, the call can be moved to the - // first block of the function where its result can be reused. One example - // where this is profitable is for uniform buffer references, of which there - // are often many. - InstructionBuilder builder(ref_builder->GetContext(), - &*ref_builder->GetInsertPoint(), - ref_builder->GetPreservedAnalysis()); - bool insert_in_first_block = opt_direct_reads_ && AllConstant(func_call_args); - if (insert_in_first_block) { - Instruction* insert_before = &*curr_func_->begin()->tail(); - builder.SetInsertPoint(insert_before); - } - uint32_t res_id = - builder.AddFunctionCall(return_id, func_id, func_call_args)->result_id(); - if (insert_in_first_block) call2id_[func_call_args] = res_id; - return res_id; -} - -bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const { - return inst->opcode() == spv::Op::OpSampledImage || - inst->opcode() == spv::Op::OpImage; -} - -void InstrumentPass::CloneSameBlockOps( - std::unique_ptr* inst, - std::unordered_map* same_blk_post, - std::unordered_map* same_blk_pre, - BasicBlock* block_ptr) { - bool changed = false; - (*inst)->ForEachInId([&same_blk_post, &same_blk_pre, &block_ptr, &changed, - 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 sb_inst(in_inst->Clone(context())); - const uint32_t rid = sb_inst->result_id(); - const uint32_t nid = this->TakeNextId(); - get_decoration_mgr()->CloneDecorations(rid, nid); - sb_inst->SetResultId(nid); - get_def_use_mgr()->AnalyzeInstDefUse(&*sb_inst); - (*same_blk_post)[rid] = nid; - *iid = nid; - changed = true; - CloneSameBlockOps(&sb_inst, same_blk_post, same_blk_pre, block_ptr); - block_ptr->AddInstruction(std::move(sb_inst)); - } - } else { - // Reset same-block op operand if necessary - if (*iid != map_itr->second) { - *iid = map_itr->second; - changed = true; - } - } - }); - if (changed) get_def_use_mgr()->AnalyzeInstUse(&**inst); -} - -void InstrumentPass::UpdateSucceedingPhis( - std::vector>& 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); - }); - }); -} - -analysis::Integer* InstrumentPass::GetInteger(uint32_t width, bool is_signed) { - analysis::Integer i(width, is_signed); - analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&i); - assert(type && type->AsInteger()); - return type->AsInteger(); -} - -analysis::Struct* InstrumentPass::GetStruct( - const std::vector& fields) { - analysis::Struct s(fields); - analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&s); - assert(type && type->AsStruct()); - return type->AsStruct(); -} - -analysis::RuntimeArray* InstrumentPass::GetRuntimeArray( - const analysis::Type* element) { - analysis::RuntimeArray r(element); - analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&r); - assert(type && type->AsRuntimeArray()); - return type->AsRuntimeArray(); -} - -analysis::Array* InstrumentPass::GetArray(const analysis::Type* element, - uint32_t length) { - uint32_t length_id = context()->get_constant_mgr()->GetUIntConstId(length); - analysis::Array::LengthInfo length_info{ - length_id, {analysis::Array::LengthInfo::Case::kConstant, length}}; - - analysis::Array r(element, length_info); - - analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&r); - assert(type && type->AsArray()); - return type->AsArray(); -} - -analysis::Function* InstrumentPass::GetFunction( - const analysis::Type* return_val, - const std::vector& args) { - analysis::Function func(return_val, args); - analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&func); - assert(type && type->AsFunction()); - return type->AsFunction(); -} - -analysis::RuntimeArray* InstrumentPass::GetUintXRuntimeArrayType( - uint32_t width, analysis::RuntimeArray** rarr_ty) { - if (*rarr_ty == nullptr) { - *rarr_ty = GetRuntimeArray(GetInteger(width, false)); - uint32_t uint_arr_ty_id = - context()->get_type_mgr()->GetTypeInstruction(*rarr_ty); - // By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of - // a block, and will therefore be decorated with an ArrayStride. Therefore - // the undecorated type returned here will not be pre-existing and can - // safely be decorated. Since this type is now decorated, it is out of - // sync with the TypeManager and therefore the TypeManager must be - // invalidated after this pass. - assert(get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 && - "used RuntimeArray type returned"); - get_decoration_mgr()->AddDecorationVal( - uint_arr_ty_id, uint32_t(spv::Decoration::ArrayStride), width / 8u); - } - return *rarr_ty; -} - -analysis::RuntimeArray* InstrumentPass::GetUintRuntimeArrayType( - uint32_t width) { - analysis::RuntimeArray** rarr_ty = - (width == 64) ? &uint64_rarr_ty_ : &uint32_rarr_ty_; - return GetUintXRuntimeArrayType(width, rarr_ty); -} - -void InstrumentPass::AddStorageBufferExt() { - if (storage_buffer_ext_defined_) return; - if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) { - context()->AddExtension("SPV_KHR_storage_buffer_storage_class"); - } - storage_buffer_ext_defined_ = true; -} - -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(); - 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::GetUint64Id() { - if (uint64_id_ == 0) { - analysis::TypeManager* type_mgr = context()->get_type_mgr(); - analysis::Integer uint64_ty(64, false); - analysis::Type* reg_uint64_ty = type_mgr->GetRegisteredType(&uint64_ty); - uint64_id_ = type_mgr->GetTypeInstruction(reg_uint64_ty); - } - 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); - analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty); - analysis::Vector v_uint_ty(reg_uint_ty, len); - analysis::Type* reg_v_uint_ty = type_mgr->GetRegisteredType(&v_uint_ty); - uint32_t v_uint_id = type_mgr->GetTypeInstruction(reg_v_uint_ty); - return v_uint_id; -} - -uint32_t InstrumentPass::GetVec4UintId() { - if (v4uint_id_ == 0) v4uint_id_ = GetVecUintId(4u); - return v4uint_id_; -} - -uint32_t InstrumentPass::GetVec3UintId() { - if (v3uint_id_ == 0) v3uint_id_ = GetVecUintId(3u); - return v3uint_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_; -} - -void InstrumentPass::SplitBlock( - BasicBlock::iterator inst_itr, UptrVectorIterator block_itr, - std::vector>* new_blocks) { - // Make sure def/use analysis is done before we start moving instructions - // out of function - (void)get_def_use_mgr(); - // Move original block's preceding instructions into first new block - std::unique_ptr first_blk_ptr; - MovePreludeCode(inst_itr, block_itr, &first_blk_ptr); - InstructionBuilder builder( - context(), &*first_blk_ptr, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - uint32_t split_blk_id = TakeNextId(); - std::unique_ptr split_label(NewLabel(split_blk_id)); - (void)builder.AddBranch(split_blk_id); - new_blocks->push_back(std::move(first_blk_ptr)); - // Move remaining instructions into split block and add to new blocks - std::unique_ptr split_blk_ptr( - new BasicBlock(std::move(split_label))); - MovePostludeCode(block_itr, &*split_blk_ptr); - new_blocks->push_back(std::move(split_blk_ptr)); -} - -bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx, - InstProcessFunction& pfn) { - curr_func_ = func; - call2id_.clear(); - bool first_block_split = false; - bool modified = false; - // Apply instrumentation function to each instruction. - // Using block iterators here because of block erasures and insertions. - std::vector> new_blks; - for (auto bi = func->begin(); bi != func->end(); ++bi) { - for (auto ii = bi->begin(); ii != bi->end();) { - // Split all executable instructions out of first block into a following - // block. This will allow function calls to be inserted into the first - // block without interfering with the instrumentation algorithm. - if (opt_direct_reads_ && !first_block_split) { - if (ii->opcode() != spv::Op::OpVariable) { - SplitBlock(ii, bi, &new_blks); - first_block_split = true; - } - } else { - pfn(ii, bi, stage_idx, &new_blks); - } - // If no new code, continue - if (new_blks.size() == 0) { - ++ii; - continue; - } - // Add new blocks to label id map - for (auto& blk : new_blks) id2block_[blk->id()] = &*blk; - // If there are new blocks we know there will always be two or - // more, so update succeeding phis with label of new last block. - size_t newBlocksSize = new_blks.size(); - 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() == spv::Op::OpPhi || - ii->opcode() == spv::Op::OpCopyObject) - ++ii; - new_blks.clear(); - } - } - return modified; -} - -bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn, - std::queue* roots, - uint32_t stage_idx) { - bool modified = false; - std::unordered_set done; - // Don't process input and output functions - for (auto& ifn : param2input_func_id_) done.insert(ifn.second); - for (auto& ofn : param2output_func_id_) done.insert(ofn.second); - // 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 - context()->AddCalls(fn, roots); - modified = InstrumentFunction(fn, stage_idx, pfn) || modified; - } - } - return modified; -} - -bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) { - uint32_t stage_id; - if (use_stage_info_) { - // 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. - spv::ExecutionModel stage = context()->GetStage(); - // Check for supported stages - if (stage != spv::ExecutionModel::Vertex && - stage != spv::ExecutionModel::Fragment && - stage != spv::ExecutionModel::Geometry && - stage != spv::ExecutionModel::GLCompute && - stage != spv::ExecutionModel::TessellationControl && - stage != spv::ExecutionModel::TessellationEvaluation && - stage != spv::ExecutionModel::TaskNV && - stage != spv::ExecutionModel::MeshNV && - stage != spv::ExecutionModel::RayGenerationNV && - stage != spv::ExecutionModel::IntersectionNV && - stage != spv::ExecutionModel::AnyHitNV && - stage != spv::ExecutionModel::ClosestHitNV && - stage != spv::ExecutionModel::MissNV && - stage != spv::ExecutionModel::CallableNV && - stage != spv::ExecutionModel::TaskEXT && - stage != spv::ExecutionModel::MeshEXT) { - if (consumer()) { - std::string message = "Stage not supported by instrumentation"; - consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str()); - } - return false; - } - stage_id = static_cast(stage); - } else { - stage_id = 0; - } - // Add together the roots of all entry points - std::queue roots; - for (auto& e : get_module()->entry_points()) { - roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)); - } - bool modified = InstProcessCallTreeFromRoots(pfn, &roots, stage_id); - return modified; -} - -void InstrumentPass::InitializeInstrument() { - float_id_ = 0; - v4float_id_ = 0; - uint_id_ = 0; - uint64_id_ = 0; - uint8_id_ = 0; - v4uint_id_ = 0; - v3uint_id_ = 0; - bool_id_ = 0; - void_id_ = 0; - storage_buffer_ext_defined_ = false; - uint32_rarr_ty_ = nullptr; - uint64_rarr_ty_ = nullptr; - - // clear collections - 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; - for (auto& blk : fn) { - id2block_[blk.id()] = &blk; - } - } - - // Remember original instruction offsets - uint32_t module_offset = 0; - Module* module = get_module(); - for (auto& i : context()->capabilities()) { - (void)i; - ++module_offset; - } - for (auto& i : module->extensions()) { - (void)i; - ++module_offset; - } - for (auto& i : module->ext_inst_imports()) { - (void)i; - ++module_offset; - } - ++module_offset; // memory_model - for (auto& i : module->entry_points()) { - (void)i; - ++module_offset; - } - for (auto& i : module->execution_modes()) { - (void)i; - ++module_offset; - } - for (auto& i : module->debugs1()) { - (void)i; - ++module_offset; - } - for (auto& i : module->debugs2()) { - (void)i; - ++module_offset; - } - for (auto& i : module->debugs3()) { - (void)i; - ++module_offset; - } - for (auto& i : module->ext_inst_debuginfo()) { - (void)i; - ++module_offset; - } - for (auto& i : module->annotations()) { - (void)i; - ++module_offset; - } - for (auto& i : module->types_values()) { - module_offset += 1; - module_offset += static_cast(i.dbg_line_insts().size()); - } - - auto curr_fn = get_module()->begin(); - for (; curr_fn != get_module()->end(); ++curr_fn) { - // Count function instruction - module_offset += 1; - curr_fn->ForEachParam( - [&module_offset](const Instruction*) { module_offset += 1; }, true); - for (auto& blk : *curr_fn) { - // Count label - module_offset += 1; - for (auto& inst : blk) { - module_offset += static_cast(inst.dbg_line_insts().size()); - uid2offset_[inst.unique_id()] = module_offset; - module_offset += 1; - } - } - // Count function end instruction - module_offset += 1; - } -} - -} // namespace opt -} // namespace spvtools diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h deleted file mode 100644 index e4408c93e..000000000 --- a/source/opt/instrument_pass.h +++ /dev/null @@ -1,326 +0,0 @@ -// 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 -#include -#include - -#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 permanently assigned and fixed and documented by -// the kDebugOutput* static consts. - -namespace spvtools { -namespace opt { - -class InstrumentPass : public Pass { - using cbb_ptr = const BasicBlock*; - - public: - using InstProcessFunction = - std::function, - uint32_t, std::vector>*)>; - - ~InstrumentPass() override = default; - - IRContext::Analysis GetPreservedAnalyses() override { - return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations | - IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap | - IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants; - } - - protected: - // Create instrumentation pass for |validation_id| which utilizes descriptor - // set |desc_set| for debug input and output buffers and writes |shader_id| - // into debug output records. |opt_direct_reads| indicates that the pass - // will see direct input buffer reads and should prepare to optimize them. - InstrumentPass(uint32_t desc_set, uint32_t shader_id, bool opt_direct_reads, - bool use_stage_info) - : Pass(), - desc_set_(desc_set), - shader_id_(shader_id), - opt_direct_reads_(opt_direct_reads), - use_stage_info_(use_stage_info) {} - - // Initialize state for instrumentation of module. - 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 ref_block_itr, - std::unique_ptr* 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 ref_block_itr, - BasicBlock* new_blk_ptr); - - // Return true if all instructions in |ids| are constants or spec constants. - bool AllConstant(const std::vector& ids); - - uint32_t GenReadFunctionCall(uint32_t return_id, uint32_t func_id, - const std::vector& args, - InstructionBuilder* builder); - - // Generate code to convert integer |value_id| to 32bit, if needed. Return - // an id to the 32bit equivalent. - uint32_t Gen32BitCvtCode(uint32_t value_id, InstructionBuilder* builder); - - // Generate code to cast integer |value_id| to 32bit unsigned, if needed. - // Return an id to the Uint equivalent. - uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder); - - std::unique_ptr StartFunction( - uint32_t func_id, const analysis::Type* return_type, - const std::vector& param_types); - - std::vector AddParameters( - Function& func, const std::vector& param_types); - - std::unique_ptr EndFunction(); - - // Return new label. - std::unique_ptr NewLabel(uint32_t label_id); - - // Set the name function parameter or local variable - std::unique_ptr NewName(uint32_t id, - const std::string& name_str); - - // Return id for 32-bit unsigned type - uint32_t GetUintId(); - - // 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(); - - // Return id for void type - uint32_t GetVoidId(); - - // Get registered type structures - analysis::Integer* GetInteger(uint32_t width, bool is_signed); - analysis::Struct* GetStruct(const std::vector& fields); - analysis::RuntimeArray* GetRuntimeArray(const analysis::Type* element); - analysis::Array* GetArray(const analysis::Type* element, uint32_t size); - analysis::Function* GetFunction( - const analysis::Type* return_val, - const std::vector& args); - - // Return pointer to type for runtime array of uint - analysis::RuntimeArray* GetUintXRuntimeArrayType( - uint32_t width, analysis::RuntimeArray** rarr_ty); - - // Return pointer to type for runtime array of uint - analysis::RuntimeArray* GetUintRuntimeArrayType(uint32_t width); - - // Add storage buffer extension if needed - void AddStorageBufferExt(); - - // Return id for 32-bit float type - uint32_t GetFloatId(); - - // Return id for v4float type - uint32_t GetVec4FloatId(); - - // Return id for uint vector type of |length| - uint32_t GetVecUintId(uint32_t length); - - // Return id for v4uint type - uint32_t GetVec4UintId(); - - // Return id for v3uint type - uint32_t GetVec3UintId(); - - // Split block |block_itr| into two new blocks where the second block - // contains |inst_itr| and place in |new_blocks|. - void SplitBlock(BasicBlock::iterator inst_itr, - UptrVectorIterator block_itr, - std::vector>* new_blocks); - - // Apply instrumentation function |pfn| to every instruction in |func|. - // If code is generated for an instruction, replace the instruction's - // block with the new blocks that are generated. Continue processing at the - // top of the last new block. - virtual 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* roots, - uint32_t stage_idx); - - // Generate instructions into |builder| which will load |var_id| and return - // its result id. - uint32_t GenVarLoad(uint32_t var_id, InstructionBuilder* builder); - - uint32_t GenStageInfo(uint32_t stage_idx, 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* inst, - std::unordered_map* same_blk_post, - std::unordered_map* same_blk_pre, - BasicBlock* block_ptr); - - // Update phis in succeeding blocks to point to new last block - void UpdateSucceedingPhis( - std::vector>& 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 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 id2block_; - - // Map from instruction's unique id to offset in original file. - std::unordered_map uid2offset_; - - // id for debug output function - std::unordered_map param2output_func_id_; - - // ids for debug input functions - std::unordered_map param2input_func_id_; - - // id for 32-bit float type - uint32_t float_id_{0}; - - // id for v4float type - uint32_t v4float_id_{0}; - - // id for v4uint type - uint32_t v4uint_id_{0}; - - // id for v3uint type - uint32_t v3uint_id_{0}; - - // id for 32-bit unsigned type - uint32_t uint_id_{0}; - - // id for 64-bit unsigned type - uint32_t uint64_id_{0}; - - // id for 8-bit unsigned type - uint32_t uint8_id_{0}; - - // id for bool type - uint32_t bool_id_{0}; - - // id for void type - uint32_t void_id_{0}; - - // boolean to remember storage buffer extension - bool storage_buffer_ext_defined_{false}; - - // runtime array of uint type - analysis::RuntimeArray* uint64_rarr_ty_{nullptr}; - - // runtime array of uint type - analysis::RuntimeArray* uint32_rarr_ty_{nullptr}; - - // Pre-instrumentation same-block insts - std::unordered_map same_block_pre_; - - // Post-instrumentation same-block op ids - std::unordered_map same_block_post_; - - // Map function calls to result id. Clear for every function. - // This is for debug input reads with constant arguments that - // have been generated into the first block of the function. - // This mechanism is used to avoid multiple identical debug - // input buffer reads. - struct vector_hash_ { - std::size_t operator()(const std::vector& v) const { - std::size_t hash = v.size(); - for (auto& u : v) { - hash ^= u + 0x9e3779b9 + (hash << 11) + (hash >> 21); - } - return hash; - } - }; - std::unordered_map, uint32_t, vector_hash_> call2id_; - - // Function currently being instrumented - Function* curr_func_{nullptr}; - - // Optimize direct debug input buffer reads. Specifically, move all such - // reads with constant args to first block and reuse them. - const bool opt_direct_reads_; - - // Set true if the instrumentation needs to know the current stage. - // Note that this does not work with multi-stage modules. - const bool use_stage_info_; -}; - -} // namespace opt -} // namespace spvtools - -#endif // LIBSPIRV_OPT_INSTRUMENT_PASS_H_ diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 22eb24c4f..94b15c6ab 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -462,13 +462,6 @@ 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") { - // This private option is not for user consumption. - // It is here to assist in debugging and fixing the debug printf - // instrumentation pass. - // For users who wish to utilize debug printf, see the white paper at - // https://www.lunarg.com/wp-content/uploads/2021/08/Using-Debug-Printf-02August2021.pdf - RegisterPass(CreateInstDebugPrintfPass(7, 23)); } else if (pass_name == "simplify-instructions") { RegisterPass(CreateSimplificationPass()); } else if (pass_name == "ssa-rewrite") { @@ -1040,12 +1033,6 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() { MakeUnique()); } -Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set, - uint32_t shader_id) { - return MakeUnique( - MakeUnique(desc_set, shader_id)); -} - Optimizer::PassToken CreateConvertRelaxedToHalfPass() { return MakeUnique( MakeUnique()); diff --git a/source/opt/passes.h b/source/opt/passes.h index f8301526f..3311529aa 100644 --- a/source/opt/passes.h +++ b/source/opt/passes.h @@ -48,7 +48,6 @@ #include "source/opt/if_conversion.h" #include "source/opt/inline_exhaustive_pass.h" #include "source/opt/inline_opaque_pass.h" -#include "source/opt/inst_debug_printf_pass.h" #include "source/opt/interface_var_sroa.h" #include "source/opt/interp_fixup_pass.h" #include "source/opt/invocation_interlock_placement_pass.h" diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt index 256997623..4ef119dea 100644 --- a/test/opt/CMakeLists.txt +++ b/test/opt/CMakeLists.txt @@ -60,7 +60,6 @@ add_spvtools_unittest(TARGET opt inline_opaque_test.cpp inline_test.cpp insert_extract_elim_test.cpp - inst_debug_printf_test.cpp instruction_list_test.cpp instruction_test.cpp interface_var_sroa_test.cpp diff --git a/test/opt/inst_debug_printf_test.cpp b/test/opt/inst_debug_printf_test.cpp deleted file mode 100644 index 24c0bc655..000000000 --- a/test/opt/inst_debug_printf_test.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) 2020-2022 Valve Corporation -// Copyright (c) 2020-2022 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 -#include - -#include "test/opt/pass_fixture.h" -#include "test/opt/pass_utils.h" - -namespace spvtools { -namespace opt { -namespace { - -static const std::string kOutputDecorations = R"( -; CHECK: OpDecorate [[output_buffer_type:%inst_printf_OutputBuffer]] Block -; CHECK: OpMemberDecorate [[output_buffer_type]] 0 Offset 0 -; CHECK: OpMemberDecorate [[output_buffer_type]] 1 Offset 4 -; CHECK: OpMemberDecorate [[output_buffer_type]] 2 Offset 8 -; CHECK: OpDecorate [[output_buffer_var:%\w+]] DescriptorSet 7 -; CHECK: OpDecorate [[output_buffer_var]] Binding 3 -)"; - -static const std::string kOutputGlobals = R"( -; CHECK: [[output_buffer_type]] = OpTypeStruct %uint %uint %_runtimearr_uint -; CHECK: [[output_ptr_type:%\w+]] = OpTypePointer StorageBuffer [[output_buffer_type]] -; CHECK: [[output_buffer_var]] = OpVariable [[output_ptr_type]] StorageBuffer -)"; - -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 -OpExecutionMode %2 OriginUpperLeft -%5 = OpString "Color is %vn" -)"; - - // clang-format off - 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 -)" + kOutputDecorations; - - 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: [[func_type:%\w+]] = OpTypeFunction %void %uint %uint %uint %uint %uint %uint %uint -; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint -)" + kOutputGlobals + R"( -; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint -; CHECK: %bool = OpTypeBool -)"; - // clang-format on - - 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: {{%\w+}} = OpCompositeExtract %float %25 0 -; CHECK: {{%\w+}} = OpBitcast %uint {{%\w+}} -; CHECK: {{%\w+}} = OpCompositeExtract %float %25 1 -; CHECK: {{%\w+}} = OpBitcast %uint {{%\w+}} -; CHECK: {{%\w+}} = OpCompositeExtract %float %25 2 -; CHECK: {{%\w+}} = OpBitcast %uint {{%\w+}} -; CHECK: {{%\w+}} = OpCompositeExtract %float %25 3 -; CHECK: {{%\w+}} = OpBitcast %uint {{%\w+}} -; CHECK: {{%\w+}} = OpFunctionCall %void %inst_printf_stream_write_5 %uint_23 %uint_36 %uint_5 {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}} -; CHECK: OpBranch {{%\w+}} -; CHECK: {{%\w+}} = OpLabel -OpStore %4 %25 -OpReturn -OpFunctionEnd -)"; - - const std::string output_func = R"( -; CHECK: %inst_printf_stream_write_5 = OpFunction %void None {{%\w+}} -; CHECK: [[sw_shader_id:%\w+]] = OpFunctionParameter %uint -; CHECK: [[sw_inst_idx:%\w+]] = OpFunctionParameter %uint -; CHECK: [[sw_param_1:%\w+]] = OpFunctionParameter %uint -; CHECK: [[sw_param_2:%\w+]] = OpFunctionParameter %uint -; CHECK: [[sw_param_3:%\w+]] = OpFunctionParameter %uint -; CHECK: [[sw_param_4:%\w+]] = OpFunctionParameter %uint -; CHECK: [[sw_param_5:%\w+]] = OpFunctionParameter %uint -; CHECK: {{%\w+}} = OpLabel -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 -; CHECK: {{%\w+}} = OpAtomicIAdd %uint {{%\w+}} %uint_4 %uint_0 %uint_8 -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_8 -; CHECK: {{%\w+}} = OpArrayLength %uint [[output_buffer_var]] 2 -; CHECK: {{%\w+}} = OpULessThanEqual %bool {{%\w+}} {{%\w+}} -; CHECK: OpSelectionMerge {{%\w+}} None -; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}} -; CHECK: {{%\w+}} = OpLabel -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_0 -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}} -; CHECK: OpStore {{%\w+}} %uint_8 -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_1 -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}} -; CHECK: OpStore {{%\w+}} [[sw_shader_id]] -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_2 -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}} -; CHECK: OpStore {{%\w+}} [[sw_inst_idx]] -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3 -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}} -; CHECK: OpStore {{%\w+}} [[sw_param_1]] -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4 -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}} -; CHECK: OpStore {{%\w+}} [[sw_param_2]] -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5 -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}} -; CHECK: OpStore {{%\w+}} [[sw_param_3]] -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_6 -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}} -; CHECK: OpStore {{%\w+}} [[sw_param_4]] -; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_7 -; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}} -; CHECK: OpStore {{%\w+}} [[sw_param_5]] -; CHECK: OpBranch {{%\w+}} -; CHECK: {{%\w+}} = OpLabel -; CHECK: OpReturn -; CHECK: OpFunctionEnd -)"; - - SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); - SinglePassRunAndMatch( - defs + decorates + globals + main + output_func, true); -} - -// TODO(greg-lunarg): Add tests to verify handling of these cases: -// -// Compute shader -// Geometry shader -// Tessellation control shader -// Tessellation eval shader -// Vertex shader - -} // namespace -} // namespace opt -} // namespace spvtools