From 64b42f6af3c8deb0986e6cff3bd6d836fbeec54b Mon Sep 17 00:00:00 2001 From: spencer-lunarg Date: Fri, 19 Jul 2024 09:06:17 -0400 Subject: [PATCH] printf: Move implementation inside VVL --- BUILD.gn | 2 + docs/debug_printf.md | 4 +- layers/gpu/debug_printf/debug_printf.cpp | 105 ++-- layers/gpu/debug_printf/debug_printf.h | 1 + .../instrumentation/gpuav_instrumentation.cpp | 3 +- layers/gpu/shaders/gpu_error_header.h | 8 + layers/gpu/spirv/CMakeLists.txt | 2 + layers/gpu/spirv/debug_printf_pass.cpp | 456 ++++++++++++++++++ layers/gpu/spirv/debug_printf_pass.h | 51 ++ layers/gpu/spirv/function_basic_block.cpp | 7 + layers/gpu/spirv/function_basic_block.h | 1 + layers/gpu/spirv/inject_function_pass.cpp | 11 +- layers/gpu/spirv/inject_function_pass.h | 7 +- layers/gpu/spirv/instruction.h | 6 + layers/gpu/spirv/module.cpp | 105 ++-- layers/gpu/spirv/module.h | 17 +- layers/gpu/spirv/pass.cpp | 5 +- layers/gpu/spirv/pass.h | 6 +- tests/spirv/instrumentation.cpp | 12 +- 19 files changed, 709 insertions(+), 100 deletions(-) create mode 100644 layers/gpu/spirv/debug_printf_pass.cpp create mode 100644 layers/gpu/spirv/debug_printf_pass.h diff --git a/BUILD.gn b/BUILD.gn index b47d135035d..a2e539da237 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -202,6 +202,8 @@ vvl_sources = [ "layers/gpu/spirv/pass.h", "layers/gpu/spirv/ray_query_pass.cpp", "layers/gpu/spirv/ray_query_pass.h", + "layers/gpu/spirv/debug_printf_pass.cpp", + "layers/gpu/spirv/debug_printf_pass.h", "layers/gpu/spirv/type_manager.cpp", "layers/gpu/spirv/type_manager.h", "layers/layer_options.cpp", diff --git a/docs/debug_printf.md b/docs/debug_printf.md index 4006a1b3be6..e8ff8e987fc 100644 --- a/docs/debug_printf.md +++ b/docs/debug_printf.md @@ -98,8 +98,7 @@ where: * `N2`, `N3`, ... are result ids of scalar and vector values to be printed * `NN` is the result id of the debug printf operation. This value is undefined. -Note that the `VK_KHR_shader_non_semantic_info` device extension must also be enabled in -the Vulkan application using this shader. +> `OpExtInstImport` of any `NonSemantic*` is properly supported with the `VK_KHR_shader_non_semantic_info` device extension. Some older compiler stacks might not handle these unknown instructions well, some will ignore it as desired. ## Debug Printf Output The strings resulting from a Debug Printf will, by default, be sent to the debug callback @@ -197,6 +196,7 @@ buffer size. * VkPhysicalDevice features: `fragmentStoresAndAtomics` and `vertexPipelineStoresAndAtomics` are required * The `VK_KHR_shader_non_semantic_info` extension must be supported and enabled + * If using the Validation Layers, we attempt to strip it out to allow wider range of users to still use Debug Printf * RenderDoc release 1.14 or later * When using Debug Printf with a debug callback, it is recommended to disable validation, as the debug level of INFO or DEBUG causes the validation layers to produce many messages diff --git a/layers/gpu/debug_printf/debug_printf.cpp b/layers/gpu/debug_printf/debug_printf.cpp index cc2e6c45d5d..4df63a7299d 100644 --- a/layers/gpu/debug_printf/debug_printf.cpp +++ b/layers/gpu/debug_printf/debug_printf.cpp @@ -16,11 +16,14 @@ */ #include "gpu/debug_printf/debug_printf.h" -#include "spirv-tools/instrument.hpp" -#include #include "generated/layer_chassis_dispatch.h" #include "state_tracker/shader_stage_state.h" #include "chassis/chassis_modification_state.h" +#include "gpu/spirv/module.h" +#include "gpu/shaders/gpu_error_header.h" + +#include +#include namespace debug_printf { @@ -47,8 +50,8 @@ void Validator::PostCreateDevice(const VkDeviceCreateInfo *pCreateInfo, const Lo use_stdout = true; } - const uint32_t kDebugOutputPrintfStream = 3; // from instrument.hpp - VkDescriptorSetLayoutBinding binding = {kDebugOutputPrintfStream, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + binding_slot_ = (uint32_t)instrumentation_bindings_.size(); // get next free binding + VkDescriptorSetLayoutBinding binding = {binding_slot_, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, kShaderStageAllGraphics | VK_SHADER_STAGE_COMPUTE_BIT | kShaderStageAllRayTracing, nullptr}; instrumentation_bindings_.push_back(binding); @@ -80,48 +83,65 @@ void Validator::DestroyBuffer(BufferInfo &buffer_info) { } } +static bool GpuValidateShader(const std::vector &input, bool SetRelaxBlockLayout, bool SetScalerBlockLayout, + spv_target_env target_env, std::string &error) { + // Use SPIRV-Tools validator to try and catch any issues with the module + spv_context ctx = spvContextCreate(target_env); + spv_const_binary_t binary{input.data(), input.size()}; + spv_diagnostic diag = nullptr; + spv_validator_options options = spvValidatorOptionsCreate(); + spvValidatorOptionsSetRelaxBlockLayout(options, SetRelaxBlockLayout); + spvValidatorOptionsSetScalarBlockLayout(options, SetScalerBlockLayout); + spv_result_t result = spvValidateWithOptions(ctx, options, &binary, &diag); + if (result != SPV_SUCCESS && diag) error = diag->error; + return (result == SPV_SUCCESS); +} + // Call the SPIR-V Optimizer to run the instrumentation pass on the shader. bool Validator::InstrumentShader(const vvl::span &input, uint32_t unique_shader_id, const Location &loc, std::vector &out_instrumented_spirv) { if (input[0] != spv::MagicNumber) return false; - // Load original shader SPIR-V - out_instrumented_spirv.clear(); - out_instrumented_spirv.reserve(input.size()); - out_instrumented_spirv.insert(out_instrumented_spirv.end(), &input.front(), &input.back() + 1); + std::vector binary; + binary.reserve(input.size()); + binary.insert(binary.end(), &input.front(), &input.back() + 1); - // Call the optimizer to instrument the shader. - // Use the unique_shader_module_id as a shader ID so we can look up its handle later in the shader_map. - // If descriptor indexing is enabled, enable length checks and updated descriptor checks - using namespace spvtools; spv_target_env target_env = PickSpirvEnv(api_version, IsExtEnabled(device_extensions.vk_khr_spirv_1_4)); - spvtools::ValidatorOptions val_options; - AdjustValidatorOptions(device_extensions, enabled_features, val_options, nullptr); - spvtools::OptimizerOptions opt_options; - opt_options.set_run_validator(true); - opt_options.set_validator_options(val_options); - Optimizer optimizer(target_env); - const spvtools::MessageConsumer debug_printf_console_message_consumer = - [this, loc](spv_message_level_t level, const char *, const spv_position_t &position, const char *message) -> void { - switch (level) { - case SPV_MSG_FATAL: - case SPV_MSG_INTERNAL_ERROR: - case SPV_MSG_ERROR: - this->LogError("UNASSIGNED-Debug-Printf", this->device, loc, - "Error during shader instrumentation in spirv-opt: line %zu: %s", position.index, message); - break; - default: - break; + if (gpuav_settings.debug_dump_instrumented_shaders) { + std::string file_name = "dump_" + std::to_string(unique_shader_id) + "_before.spv"; + std::ofstream debug_file(file_name, std::ios::out | std::ios::binary); + debug_file.write(reinterpret_cast(binary.data()), static_cast(binary.size() * sizeof(uint32_t))); + } + + gpuav::spirv::Module module(binary, unique_shader_id, desc_set_bind_index_); + + bool modified = module.RunPassDebugPrintf(binding_slot_); + if (!modified) return false; + + module.PostProcess(); + module.ToBinary(out_instrumented_spirv); + + if (gpuav_settings.debug_dump_instrumented_shaders) { + std::string file_name = "dump_" + std::to_string(unique_shader_id) + "_after.spv"; + std::ofstream debug_file(file_name, std::ios::out | std::ios::binary); + debug_file.write(reinterpret_cast(out_instrumented_spirv.data()), + static_cast(out_instrumented_spirv.size() * sizeof(uint32_t))); + } + + // (Maybe) validate the instrumented shader + if (gpuav_settings.debug_validate_instrumented_shaders) { + std::string instrumented_error; + if (!GpuValidateShader(out_instrumented_spirv, device_extensions.vk_khr_relaxed_block_layout, + device_extensions.vk_ext_scalar_block_layout, target_env, instrumented_error)) { + std::ostringstream strm; + strm << "Instrumented shader (id " << unique_shader_id << ") is invalid, spirv-val error:\n" + << instrumented_error << " Proceeding with non instrumented shader."; + InternalError(device, loc, strm.str().c_str()); + return false; } - }; - optimizer.SetMessageConsumer(debug_printf_console_message_consumer); - optimizer.RegisterPass(CreateInstDebugPrintfPass(desc_set_bind_index_, unique_shader_id)); - const bool pass = - optimizer.Run(out_instrumented_spirv.data(), out_instrumented_spirv.size(), &out_instrumented_spirv, opt_options); - if (!pass) { - InternalError(device, loc, "Failure to instrument shader in spirv-opt. Proceeding with non-instrumented shader."); } - return pass; + + return true; } // Create the instrumented shader data to provide to the driver. void Validator::PreCallRecordCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo *pCreateInfo, @@ -292,10 +312,10 @@ void Validator::AnalyzeAndGenerateMessage(VkCommandBuffer command_buffer, VkQueu // 4 Printf Format String Id // 5 Printf Values Word 0 (optional) // 6 Printf Values Word 1 (optional) - uint32_t expect = debug_output_buffer[1]; + uint32_t expect = debug_output_buffer[gpuav::kDebugPrintfOutputBufferSize]; if (!expect) return; - uint32_t index = spvtools::kDebugOutputDataOffset; + uint32_t index = gpuav::kDebugPrintfOutputBufferData; while (debug_output_buffer[index]) { std::stringstream shader_message; @@ -405,13 +425,14 @@ void Validator::AnalyzeAndGenerateMessage(VkCommandBuffer command_buffer, VkQueu } index += debug_record->size; } - if ((index - spvtools::kDebugOutputDataOffset) != expect) { + if ((index - gpuav::kDebugPrintfOutputBufferData) != expect) { std::stringstream message; message << "Debug Printf message was truncated due to a buffer size (" << printf_settings.buffer_size << ") being too small for the messages. (This can be adjusted with VK_LAYER_PRINTF_BUFFER_SIZE or vkconfig)"; InternalWarning(queue, loc, message.str().c_str()); } - memset(debug_output_buffer, 0, 4 * (debug_output_buffer[spvtools::kDebugOutputSizeOffset] + spvtools::kDebugOutputDataOffset)); + memset(debug_output_buffer, 0, + 4 * (debug_output_buffer[gpuav::kDebugPrintfOutputBufferSize] + gpuav::kDebugPrintfOutputBufferData)); } // For the given command buffer, map its debug data buffers and read their contents for analysis. @@ -691,7 +712,7 @@ void Validator::AllocateDebugPrintfResources(const VkCommandBuffer cmd_buffer, c desc_writes.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; desc_writes.pBufferInfo = &output_desc_buffer_info; desc_writes.dstSet = desc_sets[0]; - desc_writes.dstBinding = 3; + desc_writes.dstBinding = binding_slot_; DispatchUpdateDescriptorSets(device, desc_count, &desc_writes, 0, nullptr); const auto pipeline_layout = diff --git a/layers/gpu/debug_printf/debug_printf.h b/layers/gpu/debug_printf/debug_printf.h index d2008197c85..f6b94ffd0fc 100644 --- a/layers/gpu/debug_printf/debug_printf.h +++ b/layers/gpu/debug_printf/debug_printf.h @@ -190,5 +190,6 @@ class Validator : public gpu::GpuShaderInstrumentor { private: bool verbose = false; bool use_stdout = false; + uint32_t binding_slot_ = 0; }; } // namespace debug_printf diff --git a/layers/gpu/instrumentation/gpuav_instrumentation.cpp b/layers/gpu/instrumentation/gpuav_instrumentation.cpp index 63ac19e825d..f7f986dd2cb 100644 --- a/layers/gpu/instrumentation/gpuav_instrumentation.cpp +++ b/layers/gpu/instrumentation/gpuav_instrumentation.cpp @@ -99,10 +99,11 @@ bool Validator::InstrumentShader(const vvl::span &input, uint32_ return false; } - for (const auto info : module.link_info_) { + for (const auto &info : module.link_info_) { module.LinkFunction(info); } + module.PostProcess(); module.ToBinary(out_instrumented_spirv); if (gpuav_settings.debug_dump_instrumented_shaders) { diff --git a/layers/gpu/shaders/gpu_error_header.h b/layers/gpu/shaders/gpu_error_header.h index 4a14ff7a3b9..63a54488ad5 100644 --- a/layers/gpu/shaders/gpu_error_header.h +++ b/layers/gpu/shaders/gpu_error_header.h @@ -181,6 +181,14 @@ const int kErrorBufferByteSize = 4 * kErrorRecordSize * kErrorRecordCounts + 2 * #ifdef __cplusplus } // namespace glsl +#endif + +// DebugPrintf +// --- +const int kDebugPrintfOutputBufferSize = 0; +const int kDebugPrintfOutputBufferData = 1; + +#ifdef __cplusplus } // namespace gpuav #endif #endif diff --git a/layers/gpu/spirv/CMakeLists.txt b/layers/gpu/spirv/CMakeLists.txt index 8cafaf0bc11..2cf82cbb5c7 100644 --- a/layers/gpu/spirv/CMakeLists.txt +++ b/layers/gpu/spirv/CMakeLists.txt @@ -23,6 +23,8 @@ target_sources(gpu_av_spirv PRIVATE buffer_device_address_pass.cpp ray_query_pass.h ray_query_pass.cpp + debug_printf_pass.h + debug_printf_pass.cpp # Framework instruction.h diff --git a/layers/gpu/spirv/debug_printf_pass.cpp b/layers/gpu/spirv/debug_printf_pass.cpp new file mode 100644 index 00000000000..d39d9acece0 --- /dev/null +++ b/layers/gpu/spirv/debug_printf_pass.cpp @@ -0,0 +1,456 @@ +/* Copyright (c) 2024 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 "debug_printf_pass.h" +#include "module.h" +#include "gpu/shaders/gpu_error_header.h" +#include + +namespace gpuav { +namespace spirv { + +// All functions are a list of uint32_t +// The difference is just how many are passed in +uint32_t DebugPrintfPass::GetLinkFunctionId(uint32_t argument_count) { + auto it = function_id_map_.find(argument_count); + if (it != function_id_map_.end()) { + return it->second; + } + + const uint32_t link_function_id = module_.TakeNextId(); + function_id_map_[argument_count] = link_function_id; + + return link_function_id; +} + +bool DebugPrintfPass::AnalyzeInstruction(const Instruction& inst) { + if (inst.Opcode() == spv::OpExtInst && inst.Word(3) == ext_import_id_ && inst.Word(4) == NonSemanticDebugPrintfDebugPrintf) { + target_instruction_ = &inst; + return true; + } + return false; +} + +// Takes the various arguments and casts them to a valid uint32_t to be passed as a parameter in the function +void DebugPrintfPass::CreateFunctionParams(uint32_t argument_id, const Type& argument_type, std::vector& params, + BasicBlock& block, InstructionIt* inst_it) { + const uint32_t uint32_type_id = module_.type_manager_.GetTypeInt(32, false).Id(); + + switch (argument_type.spv_type_) { + case SpvType::kVector: { + const uint32_t component_count = argument_type.inst_.Word(3); + const uint32_t component_type_id = argument_type.inst_.Word(2); + const Type* component_type = module_.type_manager_.FindTypeById(component_type_id); + assert(component_type); + for (uint32_t i = 0; i < component_count; i++) { + const uint32_t extract_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpCompositeExtract, {component_type_id, extract_id, argument_id, i}, inst_it); + CreateFunctionParams(extract_id, *component_type, params, block, inst_it); + } + break; + } + + case SpvType::kInt: { + const uint32_t width = argument_type.inst_.Word(2); + + // first thing is to get any signed to unsigned via bitcast + const bool is_signed = argument_type.inst_.Word(3) != 0; + uint32_t incoming_id = argument_id; + if (is_signed) { + const uint32_t bitcast_id = module_.TakeNextId(); + const uint32_t unsigned_type_id = module_.type_manager_.GetTypeInt(width, false).Id(); + block.CreateInstruction(spv::OpBitcast, {unsigned_type_id, bitcast_id, argument_id}, inst_it); + incoming_id = bitcast_id; + } + + if (width == 8 || width == 16) { + const uint32_t uconvert_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpUConvert, {uint32_type_id, uconvert_id, incoming_id}, inst_it); + params.push_back(uconvert_id); + } else if (width == 32) { + params.push_back(incoming_id); + } else if (width == 64) { + const uint32_t uconvert_high_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpUConvert, {uint32_type_id, uconvert_high_id, incoming_id}, inst_it); + params.push_back(uconvert_high_id); + + const uint32_t uint64_type_id = module_.type_manager_.GetTypeInt(64, false).Id(); + const uint32_t shift_right_id = module_.TakeNextId(); + const uint32_t constant_32_id = module_.type_manager_.GetConstantUInt32(32).Id(); + block.CreateInstruction(spv::OpShiftRightLogical, {uint64_type_id, shift_right_id, incoming_id, constant_32_id}, + inst_it); + + const uint32_t uconvert_low_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpUConvert, {uint32_type_id, uconvert_low_id, shift_right_id}, inst_it); + params.push_back(uconvert_low_id); + } else { + assert(false && "unsupported for int width"); + } + break; + } + + case SpvType::kFloat: { + const uint32_t width = argument_type.inst_.Word(2); + if (width == 16) { + const uint32_t float32_type_id = module_.type_manager_.GetTypeFloat(32).Id(); + const uint32_t fconvert_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpFConvert, {float32_type_id, fconvert_id, argument_id}, inst_it); + + const uint32_t bitcast_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpBitcast, {uint32_type_id, bitcast_id, fconvert_id}, inst_it); + params.push_back(bitcast_id); + } else if (width == 32) { + const uint32_t bitcast_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpBitcast, {uint32_type_id, bitcast_id, argument_id}, inst_it); + params.push_back(bitcast_id); + } else if (width == 64) { + const uint32_t uint64_type_id = module_.type_manager_.GetTypeInt(64, false).Id(); + const uint32_t bitcast_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpBitcast, {uint64_type_id, bitcast_id, argument_id}, inst_it); + + const uint32_t uconvert_high_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpUConvert, {uint32_type_id, uconvert_high_id, bitcast_id}, inst_it); + params.push_back(uconvert_high_id); + + const uint32_t shift_right_id = module_.TakeNextId(); + const uint32_t constant_32_id = module_.type_manager_.GetConstantUInt32(32).Id(); + block.CreateInstruction(spv::OpShiftRightLogical, {uint64_type_id, shift_right_id, bitcast_id, constant_32_id}, + inst_it); + + const uint32_t uconvert_low_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpUConvert, {uint32_type_id, uconvert_low_id, shift_right_id}, inst_it); + params.push_back(uconvert_low_id); + } else { + assert(false && "unsupported for float width"); + } + break; + } + + case SpvType::kBool: { + // cast to uint32_t via an OpSelect + const uint32_t zero_id = module_.type_manager_.GetConstantZeroUint32().Id(); + const uint32_t one_id = module_.type_manager_.GetConstantUInt32(1).Id(); + const uint32_t select_id = module_.TakeNextId(); + block.CreateInstruction(spv::OpSelect, {uint32_type_id, select_id, argument_id, one_id, zero_id}, inst_it); + params.push_back(select_id); + break; + } + + default: + assert(false && "unsupported for function param type"); + break; + } +} + +void DebugPrintfPass::CreateFunctionCall(BasicBlock& block, InstructionIt* inst_it) { + const uint32_t inst_position = target_instruction_->position_index_; + auto inst_position_constant = module_.type_manager_.CreateConstantUInt32(inst_position); + + const uint32_t string_id = target_instruction_->Word(5); + auto string_id_constant = module_.type_manager_.CreateConstantUInt32(string_id); + + const uint32_t void_type = module_.type_manager_.GetTypeVoid().Id(); + const uint32_t function_result = module_.TakeNextId(); + + // We know the first part, then build up the rest from the printf arguments + // except the function_def, we place hold it with zero + std::vector function_call_params = {void_type, function_result, 0, inst_position_constant.Id(), + string_id_constant.Id()}; + + const uint32_t first_argument_offset = 6; + const uint32_t argument_count = target_instruction_->Length() - first_argument_offset; + for (uint32_t i = 0; i < argument_count; i++) { + const uint32_t argument_id = target_instruction_->Word(first_argument_offset + i); + const Instruction* argument_inst = nullptr; + const Constant* constant = module_.type_manager_.FindConstantById(argument_id); + if (constant) { + argument_inst = &constant->inst_; + } else { + argument_inst = block.function_.FindInstruction(argument_id); + } + assert(argument_inst); // argument is either constant or found within function block + + const Type* argument_type = module_.type_manager_.FindTypeById(argument_inst->TypeId()); + assert(argument_type); // type needs to have been declared already + + CreateFunctionParams(argument_inst->ResultId(), *argument_type, function_call_params, block, inst_it); + } + + // 3 params are the result, function type, and function ID + const uint32_t param_count = (uint32_t)function_call_params.size() - 3; + const uint32_t function_def = GetLinkFunctionId(param_count); + function_call_params[2] = function_def; + + block.CreateInstruction(spv::OpFunctionCall, function_call_params, inst_it); +} + +void DebugPrintfPass::CreateDescriptorSet() { + // Create descriptor set to match output buffer + // The following is what the GLSL would look like + // + // layout(set = kSet, binding = kBinding, std430) buffer SSBO { + // uint written_count; + // uint data[]; + // } output_buffer; + + const Type& uint32_type = module_.type_manager_.GetTypeInt(32, false); + const uint32_t runtime_array_type_id = module_.type_manager_.GetTypeRuntimeArray(uint32_type).Id(); + + // if 2 OpTypeRuntimeArray are combined, we can't have ArrayStride twice + bool has_array_stride = false; + for (auto& inst : module_.annotations_) { + if (inst->Opcode() == spv::OpDecorate && inst->Word(1) == runtime_array_type_id && + inst->Word(2) == spv::DecorationArrayStride) { + has_array_stride = true; + break; + } + } + if (!has_array_stride) { + module_.AddDecoration(runtime_array_type_id, spv::DecorationArrayStride, {4}); + } + + const uint32_t struct_type_id = module_.TakeNextId(); + auto new_struct_inst = std::make_unique(4, spv::OpTypeStruct); + new_struct_inst->Fill({struct_type_id, uint32_type.Id(), runtime_array_type_id}); + const Type& struct_type = module_.type_manager_.AddType(std::move(new_struct_inst), SpvType::kStruct); + module_.AddDecoration(struct_type_id, spv::DecorationBlock, {}); + module_.AddMemberDecoration(struct_type_id, gpuav::kDebugPrintfOutputBufferSize, spv::DecorationOffset, {0}); + module_.AddMemberDecoration(struct_type_id, gpuav::kDebugPrintfOutputBufferData, spv::DecorationOffset, {4}); + + // create a storage buffer interface variable + const Type& pointer_type = module_.type_manager_.GetTypePointer(spv::StorageClassStorageBuffer, struct_type); + output_buffer_variable_id_ = module_.TakeNextId(); + auto new_inst = std::make_unique(4, spv::OpVariable); + new_inst->Fill({pointer_type.Id(), output_buffer_variable_id_, spv::StorageClassStorageBuffer}); + module_.type_manager_.AddVariable(std::move(new_inst), pointer_type); + module_.AddInterfaceVariables(output_buffer_variable_id_, spv::StorageClassStorageBuffer); + + module_.AddDecoration(output_buffer_variable_id_, spv::DecorationDescriptorSet, {module_.output_buffer_descriptor_set_}); + module_.AddDecoration(output_buffer_variable_id_, spv::DecorationBinding, {binding_slot_}); +} + +void DebugPrintfPass::CreateBufferWriteFunction(uint32_t argument_count, uint32_t function_id) { + // Currently this is generated by the number of arguments + // The following is what the GLSL would look like + // + // void inst_debug_printf_5(uint a, uint b, uint c, uint d) { + // uint offset = atomicAdd(output_buffer.written_count, 5); + // if ((offset + 5) <= uint(output_buffer.data.length())) { + // output_buffer.data[offset + 0] = 5; + // output_buffer.data[offset + 1] = a; + // output_buffer.data[offset + 2] = b; + // output_buffer.data[offset + 3] = c; + // output_buffer.data[offset + 4] = d; + // } + // } + + // Need 1 byte to write the "how many bytes will there be" + // Need 1 byte for the shader stage (which we don't pass in as we know already) + const uint32_t byte_written = argument_count + 2; + + // Debug name is matching number of bytes written into the buffer + std::string function_name = "inst_debug_printf_" + std::to_string(byte_written); + module_.AddDebugName(function_name.c_str(), function_id); + + // Need to create the function type + const uint32_t function_type_id = module_.TakeNextId(); + const uint32_t void_type_id = module_.type_manager_.GetTypeVoid().Id(); + const uint32_t uint32_type_id = module_.type_manager_.GetTypeInt(32, false).Id(); + { + std::vector words = {function_type_id, void_type_id}; + for (size_t i = 0; i < argument_count; i++) { + words.push_back(uint32_type_id); + } + auto new_inst = std::make_unique(argument_count + 3, spv::OpTypeFunction); + new_inst->Fill(words); + module_.type_manager_.AddType(std::move(new_inst), SpvType::kFunction); + } + + auto& new_function = module_.functions_.emplace_back(std::make_unique(module_)); + std::vector function_param_ids; + { + auto new_inst = std::make_unique(5, spv::OpFunction); + new_inst->Fill({void_type_id, function_id, spv::FunctionControlMaskNone, function_type_id}); + new_function->pre_block_inst_.emplace_back(std::move(new_inst)); + + for (size_t i = 0; i < argument_count; i++) { + const uint32_t new_id = module_.TakeNextId(); + auto param_inst = std::make_unique(3, spv::OpFunctionParameter); + param_inst->Fill({uint32_type_id, new_id}); + new_function->pre_block_inst_.emplace_back(std::move(param_inst)); + function_param_ids.push_back(new_id); + } + } + + new_function->InitBlocks(3); + auto& check_block = new_function->blocks_[0]; + auto& store_block = new_function->blocks_[1]; + auto& merge_block = new_function->blocks_[2]; + + const Type& uint32_type = module_.type_manager_.GetTypeInt(32, false); + const uint32_t pointer_type_id = module_.type_manager_.GetTypePointer(spv::StorageClassStorageBuffer, uint32_type).Id(); + const uint32_t zero_id = module_.type_manager_.GetConstantZeroUint32().Id(); + const uint32_t one_id = module_.type_manager_.GetConstantUInt32(1).Id(); + const uint32_t byte_written_id = module_.type_manager_.GetConstantUInt32(byte_written).Id(); + uint32_t atomic_add_id = 0; + + // Add atomic and check if buffer size is large enough + { + const uint32_t access_chain_id = module_.TakeNextId(); + check_block->CreateInstruction(spv::OpAccessChain, {pointer_type_id, access_chain_id, output_buffer_variable_id_, zero_id}); + + atomic_add_id = module_.TakeNextId(); + const uint32_t scope_invok_id = module_.type_manager_.GetConstantUInt32(spv::ScopeInvocation).Id(); + const uint32_t mask_none_id = module_.type_manager_.GetConstantUInt32(spv::MemoryAccessMaskNone).Id(); + check_block->CreateInstruction( + spv::OpAtomicIAdd, {uint32_type_id, atomic_add_id, access_chain_id, scope_invok_id, mask_none_id, byte_written_id}); + + const uint32_t int_add_id = module_.TakeNextId(); + check_block->CreateInstruction(spv::OpIAdd, {uint32_type_id, int_add_id, atomic_add_id, byte_written_id}); + + const uint32_t array_length_id = module_.TakeNextId(); + check_block->CreateInstruction(spv::OpArrayLength, {uint32_type_id, array_length_id, output_buffer_variable_id_, 1}); + + const uint32_t less_than_equal_id = module_.TakeNextId(); + const uint32_t bool_type_id = module_.type_manager_.GetTypeBool().Id(); + check_block->CreateInstruction(spv::OpULessThanEqual, {bool_type_id, less_than_equal_id, int_add_id, array_length_id}); + + const uint32_t merge_block_label_id = merge_block->GetLabelId(); + check_block->CreateInstruction(spv::OpSelectionMerge, {merge_block_label_id, spv::SelectionControlMaskNone}); + + const uint32_t store_block_label_id = store_block->GetLabelId(); + check_block->CreateInstruction(spv::OpBranchConditional, {less_than_equal_id, store_block_label_id, merge_block_label_id}); + } + + // Store how many bytes + { + const uint32_t int_add_id = module_.TakeNextId(); + store_block->CreateInstruction(spv::OpIAdd, {uint32_type_id, int_add_id, atomic_add_id, zero_id}); + + const uint32_t access_chain_id = module_.TakeNextId(); + store_block->CreateInstruction(spv::OpAccessChain, + {pointer_type_id, access_chain_id, output_buffer_variable_id_, one_id, int_add_id}); + + store_block->CreateInstruction(spv::OpStore, {access_chain_id, byte_written_id}); + } + + // Store Shader Stage ID + { + const uint32_t int_add_id = module_.TakeNextId(); + store_block->CreateInstruction(spv::OpIAdd, {uint32_type_id, int_add_id, atomic_add_id, one_id}); + + const uint32_t access_chain_id = module_.TakeNextId(); + store_block->CreateInstruction(spv::OpAccessChain, + {pointer_type_id, access_chain_id, output_buffer_variable_id_, one_id, int_add_id}); + + const uint32_t shader_id = module_.type_manager_.GetConstantUInt32(module_.shader_id_).Id(); + store_block->CreateInstruction(spv::OpStore, {access_chain_id, shader_id}); + } + + // Write a byte for each argument + for (uint32_t i = 0; i < argument_count; i++) { + const uint32_t int_add_id = module_.TakeNextId(); + const uint32_t offset_id = module_.type_manager_.GetConstantUInt32(i + 2).Id(); + store_block->CreateInstruction(spv::OpIAdd, {uint32_type_id, int_add_id, atomic_add_id, offset_id}); + + const uint32_t access_chain_id = module_.TakeNextId(); + store_block->CreateInstruction(spv::OpAccessChain, + {pointer_type_id, access_chain_id, output_buffer_variable_id_, one_id, int_add_id}); + + store_block->CreateInstruction(spv::OpStore, {access_chain_id, function_param_ids[i]}); + } + + // merge block of the above if() check + { + store_block->CreateInstruction(spv::OpBranch, {merge_block->GetLabelId()}); + merge_block->CreateInstruction(spv::OpReturn, {}); + } + + { + auto new_inst = std::make_unique(1, spv::OpFunctionEnd); + new_function->post_block_inst_.emplace_back(std::move(new_inst)); + } +} + +void DebugPrintfPass::Reset() { target_instruction_ = nullptr; } + +bool DebugPrintfPass::Run() { + for (const auto& inst : module_.ext_inst_imports_) { + const char* import_string = inst->GetAsString(2); + if (strcmp(import_string, "NonSemantic.DebugPrintf") == 0) { + ext_import_id_ = inst->ResultId(); + break; + } + } + + if (ext_import_id_ == 0) { + return false; // no printf strings found, early return + } + + bool found_one_inst = false; + for (const auto& function : module_.functions_) { + for (auto block_it = function->blocks_.begin(); block_it != function->blocks_.end(); ++block_it) { + auto& block_instructions = (*block_it)->instructions_; + for (auto inst_it = block_instructions.begin(); inst_it != block_instructions.end(); ++inst_it) { + if (!AnalyzeInstruction(*(inst_it->get()))) continue; + found_one_inst = true; + + CreateFunctionCall(**block_it, &inst_it); + + // remove the OpExtInst incase they don't support VK_KHR_non_semantic_info + inst_it = block_instructions.erase(inst_it); + inst_it--; + + Reset(); + } + } + } + if (!found_one_inst) { + return false; + } + + CreateDescriptorSet(); + + // Here we "link" the functions, but since it is all generated, no need to go through the LinkInfo flow + for (const auto& entry : function_id_map_) { + CreateBufferWriteFunction(entry.first, entry.second); + } + + // remove the everything else possible incase they don't support VK_KHR_non_semantic_info + bool other_non_semantic = false; + for (auto inst_it = module_.ext_inst_imports_.begin(); inst_it != module_.ext_inst_imports_.end(); ++inst_it) { + const char* import_string = (inst_it->get())->GetAsString(2); + if (strcmp(import_string, "NonSemantic.DebugPrintf") == 0) { + module_.ext_inst_imports_.erase(inst_it); + break; + } else if (strcmp(import_string, "NonSemantic.") == 0) { + other_non_semantic = true; + } + } + if (!other_non_semantic) { + for (auto inst_it = module_.extensions_.begin(); inst_it != module_.extensions_.end(); ++inst_it) { + const char* import_string = (inst_it->get())->GetAsString(1); + if (strcmp(import_string, "SPV_KHR_non_semantic_info") == 0) { + module_.extensions_.erase(inst_it); + break; + } + } + } + + return true; +} + +} // namespace spirv +} // namespace gpuav \ No newline at end of file diff --git a/layers/gpu/spirv/debug_printf_pass.h b/layers/gpu/spirv/debug_printf_pass.h new file mode 100644 index 00000000000..cd50e12718a --- /dev/null +++ b/layers/gpu/spirv/debug_printf_pass.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2024 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. + */ +#pragma once + +#include +#include "pass.h" + +namespace gpuav { +namespace spirv { + +struct Type; + +// Create a pass to instrument NonSemantic.DebugPrintf (GL_EXT_debug_printf) instructions +class DebugPrintfPass : public Pass { + public: + DebugPrintfPass(Module& module, uint32_t binding_slot = 0) : Pass(module), binding_slot_(binding_slot) {} + bool Run() override; + + private: + bool AnalyzeInstruction(const Instruction& inst); + void CreateFunctionCall(BasicBlock& block, InstructionIt* inst_it); + void CreateFunctionParams(uint32_t argument_id, const Type& argument_type, std::vector& params, BasicBlock& block, + InstructionIt* inst_it); + void CreateDescriptorSet(); + void CreateBufferWriteFunction(uint32_t argument_count, uint32_t function_id); + void Reset() final; + + const uint32_t binding_slot_; + uint32_t ext_import_id_ = 0; + + // + vvl::unordered_map function_id_map_; + uint32_t GetLinkFunctionId(uint32_t argument_count); + + uint32_t output_buffer_variable_id_ = 0; +}; + +} // namespace spirv +} // namespace gpuav \ No newline at end of file diff --git a/layers/gpu/spirv/function_basic_block.cpp b/layers/gpu/spirv/function_basic_block.cpp index 14804c27b0d..b83c1d25751 100644 --- a/layers/gpu/spirv/function_basic_block.cpp +++ b/layers/gpu/spirv/function_basic_block.cpp @@ -117,6 +117,13 @@ BasicBlockIt Function::InsertNewBlock(BasicBlockIt it) { return new_block_it; } +void Function::InitBlocks(uint32_t count) { + for (uint32_t i = 0; i < count; i++) { + auto new_block = std::make_unique(module_, *this); + blocks_.emplace_back(std::move(new_block)); + } +} + const Instruction* Function::FindInstruction(uint32_t id) const { auto it = inst_map_.find(id); return (it == inst_map_.end()) ? nullptr : it->second; diff --git a/layers/gpu/spirv/function_basic_block.h b/layers/gpu/spirv/function_basic_block.h index 30e29b98f1f..b4fd0ca1f7a 100644 --- a/layers/gpu/spirv/function_basic_block.h +++ b/layers/gpu/spirv/function_basic_block.h @@ -73,6 +73,7 @@ struct Function { // Adds a new block after and returns reference to it BasicBlockIt InsertNewBlock(BasicBlockIt it); + void InitBlocks(uint32_t count); void ReplaceAllUsesWith(uint32_t old_word, uint32_t new_word); diff --git a/layers/gpu/spirv/inject_function_pass.cpp b/layers/gpu/spirv/inject_function_pass.cpp index 7f4e62a3c81..7dcc3d94b0d 100644 --- a/layers/gpu/spirv/inject_function_pass.cpp +++ b/layers/gpu/spirv/inject_function_pass.cpp @@ -19,6 +19,11 @@ namespace gpuav { namespace spirv { +InjectFunctionPass::InjectFunctionPass(Module& module, bool conditional_function_check) + : Pass(module), conditional_function_check_(conditional_function_check) { + module.use_bda_ = true; +} + BasicBlockIt InjectFunctionPass::InjectConditionalFunctionCheck(Function* function, BasicBlockIt block_it, InstructionIt inst_it, const InjectionData& injection_data) { // We turn the block into 4 separate blocks @@ -117,7 +122,7 @@ void InjectFunctionPass::InjectFunctionCheck(BasicBlockIt block_it, InstructionI Reset(); } -void InjectFunctionPass::Run() { +bool InjectFunctionPass::Run() { // Can safely loop function list as there is no injecting of new Functions until linking time for (const auto& function : module_.functions_) { for (auto block_it = function->blocks_.begin(); block_it != function->blocks_.end(); ++block_it) { @@ -130,7 +135,7 @@ void InjectFunctionPass::Run() { if (!AnalyzeInstruction(*function, *(inst_it->get()))) continue; if (module_.max_instrumented_count_ != 0 && instrumented_count_ >= module_.max_instrumented_count_) { - return; + return true; // hit limit } instrumented_count_++; @@ -153,6 +158,8 @@ void InjectFunctionPass::Run() { } } } + + return instrumented_count_ != 0; } } // namespace spirv diff --git a/layers/gpu/spirv/inject_function_pass.h b/layers/gpu/spirv/inject_function_pass.h index b527bf93363..a9cc6024a6c 100644 --- a/layers/gpu/spirv/inject_function_pass.h +++ b/layers/gpu/spirv/inject_function_pass.h @@ -28,11 +28,10 @@ struct InjectionData { // A type of common pass that will inject a function call and link it up later class InjectFunctionPass : public Pass { public: - void Run() override; + bool Run() override; protected: - InjectFunctionPass(Module& module, bool conditional_function_check) - : Pass(module), conditional_function_check_(conditional_function_check) {} + InjectFunctionPass(Module& module, bool conditional_function_check); BasicBlockIt InjectConditionalFunctionCheck(Function* function, BasicBlockIt block_it, InstructionIt inst_it, const InjectionData& injection_data); @@ -44,8 +43,6 @@ class InjectFunctionPass : public Pass { // Each pass creates a OpFunctionCall and returns its result id. // If |inst_it| is not null, it will update it to instruction post OpFunctionCall virtual uint32_t CreateFunctionCall(BasicBlock& block, InstructionIt* inst_it, const InjectionData& injection_data) = 0; - // clear values incase multiple injections are made - virtual void Reset() = 0; // If this is false, we assume through other means (such as robustness) we won't crash on bad values and go // PassFunction(original_value) diff --git a/layers/gpu/spirv/instruction.h b/layers/gpu/spirv/instruction.h index be1e5e0396a..774ce9576f2 100644 --- a/layers/gpu/spirv/instruction.h +++ b/layers/gpu/spirv/instruction.h @@ -62,6 +62,12 @@ struct Instruction { bool IsArray() const { return (Opcode() == spv::OpTypeArray || Opcode() == spv::OpTypeRuntimeArray); } + // SPIR-V spec: "A string is interpreted as a nul-terminated stream of characters" + char const* GetAsString(uint32_t index) const { + assert(index < Length()); + return (char const*)&words_[index]; + } + void ToBinary(std::vector& out); bool operator==(Instruction const& other) const { return words_ == other.words_; } diff --git a/layers/gpu/spirv/module.cpp b/layers/gpu/spirv/module.cpp index 071294bf9ed..bd8c16cd534 100644 --- a/layers/gpu/spirv/module.cpp +++ b/layers/gpu/spirv/module.cpp @@ -20,6 +20,7 @@ #include "buffer_device_address_pass.h" #include "bindless_descriptor_pass.h" #include "ray_query_pass.h" +#include "debug_printf_pass.h" namespace gpuav { namespace spirv { @@ -224,19 +225,52 @@ void Module::AddExtension(const char* extension) { extensions_.push_back(std::move(new_inst)); } -void Module::RunPassBindlessDescriptor() { +void Module::AddDebugName(const char* name, uint32_t id) { + std::vector words = {id}; + StringToSpirv(name, words); + auto new_inst = std::make_unique((uint32_t)(words.size() + 1), spv::OpName); + new_inst->Fill(words); + debug_name_.emplace_back(std::move(new_inst)); +} + +void Module::AddDecoration(uint32_t target_id, spv::Decoration decoration, const std::vector& operands) { + auto new_inst = std::make_unique((uint32_t)(operands.size() + 3), spv::OpDecorate); + new_inst->Fill({target_id, (uint32_t)decoration}); + if (!operands.empty()) { + new_inst->Fill(operands); + } + annotations_.push_back(std::move(new_inst)); +} + +void Module::AddMemberDecoration(uint32_t target_id, uint32_t index, spv::Decoration decoration, + const std::vector& operands) { + auto new_inst = std::make_unique((uint32_t)(operands.size() + 4), spv::OpMemberDecorate); + new_inst->Fill({target_id, index, (uint32_t)decoration}); + if (!operands.empty()) { + new_inst->Fill(operands); + } + annotations_.push_back(std::move(new_inst)); +} + +bool Module::RunPassBindlessDescriptor() { BindlessDescriptorPass pass(*this); - pass.Run(); + return pass.Run(); } -void Module::RunPassBufferDeviceAddress() { +bool Module::RunPassBufferDeviceAddress() { BufferDeviceAddressPass pass(*this); - pass.Run(); + return pass.Run(); } -void Module::RunPassRayQuery() { +bool Module::RunPassRayQuery() { RayQueryPass pass(*this); - pass.Run(); + return pass.Run(); +} + +// binding slot allows debug printf to be slotted in the same set as GPU-AV if needed +bool Module::RunPassDebugPrintf(uint32_t binding_slot) { + DebugPrintfPass pass(*this, binding_slot); + return pass.Run(); } uint32_t Module::TakeNextId() { @@ -292,6 +326,19 @@ void Module::ToBinary(std::vector& out) { } } +// We need to apply variable to the Entry Point interface if using SPIR-V 1.4+ (or input/output) +void Module::AddInterfaceVariables(uint32_t id, spv::StorageClass storage_class) { + const uint32_t spirv_version_1_4 = 0x00010400; + if (header_.version >= spirv_version_1_4 || storage_class == spv::StorageClassInput || + storage_class == spv::StorageClassOutput) { + // Currently just apply to all Entrypoint as it should be ok to have a global variable in there even if it can't dynamically + // touch the new function + for (auto& entry_point : entry_points_) { + entry_point->AppendWord(id); + } + } +} + // Takes the current module and injects the function into it // This is done by first apply any new Types/Constants/Variables and then copying in the instructions of the Function void Module::LinkFunction(const LinkInfo& info) { @@ -303,17 +350,6 @@ void Module::LinkFunction(const LinkInfo& info) { // Track all decorations and add after when have full id_swap_map InstructionList decorations; - // We need to apply variable to the Entry Point interface if using SPIR-V 1.4+ - std::vector interface_variable_ids; - - // Adjust the original addressing model to be PhysicalStorageBuffer64 if not already. - // A module can only have one OpMemoryModel - memory_model_[0]->words_[1] = spv::AddressingModelPhysicalStorageBuffer64; - if (!HasCapability(spv::CapabilityPhysicalStorageBufferAddresses)) { - AddCapability(spv::CapabilityPhysicalStorageBufferAddresses); - AddExtension("SPV_KHR_physical_storage_buffer"); - } - // find all constant and types, add any the module doesn't have uint32_t offset = 5; // skip header while (offset < info.word_count) { @@ -456,7 +492,7 @@ void Module::LinkFunction(const LinkInfo& info) { } else if (opcode == spv::OpVariable) { // Add in all variables outside of functions const uint32_t new_result_id = TakeNextId(); - interface_variable_ids.push_back(new_result_id); + AddInterfaceVariables(new_result_id, (spv::StorageClass)new_inst->Word(3)); id_swap_map[old_result_id] = new_result_id; new_inst->ReplaceResultId(new_result_id); new_inst->ReplaceLinkedId(id_swap_map); @@ -494,13 +530,7 @@ void Module::LinkFunction(const LinkInfo& info) { offset_copy += length; } - { - std::vector words = {info.function_id}; - StringToSpirv(info.opname, words); - auto new_inst = std::make_unique((uint32_t)(words.size() + 1), spv::OpName); - new_inst->Fill(words); - debug_name_.emplace_back(std::move(new_inst)); - } + AddDebugName(info.opname, info.function_id); // Add function and copy all instructions to it, while adjusting any IDs auto& new_function = functions_.emplace_back(std::make_unique(*this)); @@ -559,6 +589,19 @@ void Module::LinkFunction(const LinkInfo& info) { annotations_.push_back(std::move(decoration)); } +} + +// Things that need to be done once if there is any instrumentation. +void Module::PostProcess() { + if (use_bda_) { + // Adjust the original addressing model to be PhysicalStorageBuffer64 if not already. + // A module can only have one OpMemoryModel + memory_model_[0]->words_[1] = spv::AddressingModelPhysicalStorageBuffer64; + if (!HasCapability(spv::CapabilityPhysicalStorageBufferAddresses)) { + AddCapability(spv::CapabilityPhysicalStorageBufferAddresses); + AddExtension("SPV_KHR_physical_storage_buffer"); + } + } // The instrumentation code has atomicAdd() to update the output buffer // If the incoming code only has VulkanMemoryModel it will need to support device scope @@ -567,18 +610,6 @@ void Module::LinkFunction(const LinkInfo& info) { AddCapability(spv::CapabilityVulkanMemoryModelDeviceScope); } - // Update entrypoint interface if 1.4+ - const uint32_t spirv_version_1_4 = 0x00010400; - if (header_.version >= spirv_version_1_4) { - // Currently just apply to all Entrypoint as it should be ok to have a global variable in there even if it can't dynamically - // touch the new function - for (auto& entry_point : entry_points_) { - for (uint32_t id : interface_variable_ids) { - entry_point->AppendWord(id); - } - } - } - // Vulkan 1.1 is required, so if incoming SPIR-V is 1.0, might need to adjust it const uint32_t spirv_version_1_0 = 0x00010000; if (header_.version == spirv_version_1_0) { diff --git a/layers/gpu/spirv/module.h b/layers/gpu/spirv/module.h index ec1fe8163fb..3f8a085a483 100644 --- a/layers/gpu/spirv/module.h +++ b/layers/gpu/spirv/module.h @@ -67,23 +67,30 @@ class Module { std::vector link_info_; void LinkFunction(const LinkInfo& info); bool IsInstrumented() const { return !link_info_.empty(); } + void PostProcess(); // The class is designed to be written out to a binary file. void ToBinary(std::vector& out); // Passes that can be ran - void RunPassBindlessDescriptor(); - void RunPassBufferDeviceAddress(); - void RunPassRayQuery(); + // Return true if code was instrumented + bool RunPassBindlessDescriptor(); + bool RunPassBufferDeviceAddress(); + bool RunPassRayQuery(); + bool RunPassDebugPrintf(uint32_t binding_slot = 0); + + void AddInterfaceVariables(uint32_t id, spv::StorageClass storage_class); // Helpers bool HasCapability(spv::Capability capability); void AddCapability(spv::Capability capability); void AddExtension(const char* extension); + void AddDebugName(const char* name, uint32_t id); + void AddDecoration(uint32_t target_id, spv::Decoration decoration, const std::vector& operands); + void AddMemberDecoration(uint32_t target_id, uint32_t index, spv::Decoration decoration, const std::vector& operands); const uint32_t max_instrumented_count_ = 0; // zero is same as "unlimited" - - private: + bool use_bda_ = false; // provides a way to map back and know which original SPIR-V this was from const uint32_t shader_id_; // Will replace the "OpDecorate DescriptorSet" for the output buffer in the incoming linked module diff --git a/layers/gpu/spirv/pass.cpp b/layers/gpu/spirv/pass.cpp index a3b56a2e93d..78629a335fd 100644 --- a/layers/gpu/spirv/pass.cpp +++ b/layers/gpu/spirv/pass.cpp @@ -45,10 +45,7 @@ const Variable& Pass::GetBuiltinVariable(uint32_t built_in) { auto new_inst = std::make_unique(4, spv::OpVariable); new_inst->Fill({pointer_type.Id(), variable_id, spv::StorageClassInput}); built_in_variable = &module_.type_manager_.AddVariable(std::move(new_inst), pointer_type); - - for (auto& entry_point : module_.entry_points_) { - entry_point->AppendWord(built_in_variable->Id()); - } + module_.AddInterfaceVariables(built_in_variable->Id(), spv::StorageClassInput); } return *built_in_variable; diff --git a/layers/gpu/spirv/pass.h b/layers/gpu/spirv/pass.h index 8650e00902b..38564905357 100644 --- a/layers/gpu/spirv/pass.h +++ b/layers/gpu/spirv/pass.h @@ -28,7 +28,7 @@ struct BasicBlock; // Common helpers for all passes class Pass { public: - virtual void Run() {} + virtual bool Run() { return false; } // Finds (and creates if needed) decoration and returns the OpVariable it points to const Variable& GetBuiltinVariable(uint32_t built_in); @@ -48,7 +48,11 @@ class Pass { Pass(Module& module) : module_(module) {} Module& module_; + // clear values between instrumented instructions + virtual void Reset() = 0; + // As various things are modifiying the instruction streams, we need to get back to where we were. + // (normally set in the AnalyzeInstruction call) const Instruction* target_instruction_ = nullptr; InstructionIt FindTargetInstruction(BasicBlock& block) const; }; diff --git a/tests/spirv/instrumentation.cpp b/tests/spirv/instrumentation.cpp index 0f68c573284..a997edd7e30 100644 --- a/tests/spirv/instrumentation.cpp +++ b/tests/spirv/instrumentation.cpp @@ -26,6 +26,7 @@ static bool all_passes = false; static bool bindless_descriptor_pass = false; static bool buffer_device_address_pass = false; static bool ray_query_pass = false; +static bool debug_printf_pass = false; void PrintUsage(const char* program) { printf(R"( @@ -44,6 +45,8 @@ USAGE: %s -o Runs BufferDeviceAddressPass --ray-query Runs RayQueryPass + --debug-printf + Runs DebugPrintfPass --timer Prints time it takes to instrument entire module -h, --help @@ -75,6 +78,8 @@ bool ParseFlags(int argc, char** argv, const char** out_file) { buffer_device_address_pass = true; } else if (0 == strcmp(cur_arg, "--ray-query")) { ray_query_pass = true; + } else if (0 == strcmp(cur_arg, "--debug-printf")) { + debug_printf_pass = true; } else if (0 == strncmp(cur_arg, "--", 2)) { printf("Unknown pass %s\n", cur_arg); PrintUsage(argv[0]); @@ -133,10 +138,15 @@ int main(int argc, char** argv) { if (all_passes || ray_query_pass) { module.RunPassRayQuery(); } + if (all_passes || debug_printf_pass) { + module.RunPassDebugPrintf(); + } - for (const auto info : module.link_info_) { + for (const auto& info : module.link_info_) { module.LinkFunction(info); } + + module.PostProcess(); module.ToBinary(spirv_data); if (timer) {