| /* Copyright (c) 2024-2026 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 "log_error_pass.h" |
| #include "module.h" |
| #include "containers/container_utils.h" |
| #include <spirv/unified1/spirv.hpp> |
| #include <iostream> |
| |
| #include "generated/gpuav_offline_spirv.h" |
| |
| namespace gpuav { |
| namespace spirv { |
| |
| const static OfflineModule kOfflineModule = {instrumentation_log_error_comp, instrumentation_log_error_comp_size, |
| UseErrorPayloadVariable}; |
| const static OfflineFunction kOfflineFunction = {"inst_log_error", instrumentation_log_error_comp_function_0_offset}; |
| |
| LogErrorPass::LogErrorPass(Module& module) : Pass(module, kOfflineModule) { module.use_bda_ = true; } |
| |
| void LogErrorPass::PrintDebugInfo() const { |
| std::cout << "LogErrorPass instrumentation count: " << instrumentations_count_ << '\n'; |
| } |
| |
| void LogErrorPass::ClearErrorPayloadVariable(Function& function) { |
| // First time we clear, we add the private variable to the global scope |
| if (module_.error_payload_variable_id_ == 0) { |
| module_.error_payload_variable_id_ = module_.TakeNextId(); |
| const Type& uint32_type = type_manager_.GetTypeInt(32, false); |
| |
| const uint32_t struct_type_id = module_.TakeNextId(); |
| auto new_struct_inst = std::make_unique<Instruction>(7, spv::OpTypeStruct); |
| new_struct_inst->Fill({ |
| struct_type_id, |
| uint32_type.Id(), // uint inst_offset; |
| uint32_type.Id(), // uint shader_error_encoding; |
| uint32_type.Id(), // uint parameter_0; |
| uint32_type.Id(), // uint parameter_1; |
| uint32_type.Id(), // uint parameter_2; |
| }); |
| const Type& struct_type = type_manager_.AddType(std::move(new_struct_inst), SpvType::kStruct); |
| type_manager_.AddStructTypeForLinking(&struct_type); |
| const Type& pointer_type = type_manager_.GetTypePointer(spv::StorageClassPrivate, struct_type); |
| { |
| auto new_inst = std::make_unique<Instruction>(4, spv::OpVariable); |
| new_inst->Fill({pointer_type.Id(), module_.error_payload_variable_id_, spv::StorageClassPrivate}); |
| type_manager_.AddVariable(std::move(new_inst), pointer_type); |
| } |
| |
| const uint32_t uint32_0_id = type_manager_.GetConstantZeroUint32().Id(); |
| const uint32_t constant_id = module_.TakeNextId(); |
| { |
| auto new_inst = std::make_unique<Instruction>(8, spv::OpConstantComposite); |
| new_inst->Fill({struct_type.Id(), constant_id, uint32_0_id, uint32_0_id, uint32_0_id, uint32_0_id, uint32_0_id}); |
| const Constant& clear_constant = type_manager_.AddConstant(std::move(new_inst), struct_type); |
| error_payload_variable_clear_ = clear_constant.Id(); |
| } |
| } |
| |
| BasicBlock& block = function.GetFirstBlock(); |
| InstructionIt inst_it = block.GetFirstInjectableInstrution(); |
| |
| block.CreateInstruction(spv::OpStore, {module_.error_payload_variable_id_, error_payload_variable_clear_}, &inst_it); |
| } |
| |
| void LogErrorPass::CreateFunctionCallLogError(Function& function, BasicBlock& block, InstructionIt* inst_it) { |
| GetLinkFunction(link_function_id_, kOfflineFunction); |
| |
| const uint32_t stage_info_id = GetStageInfo(function, block, *inst_it); |
| |
| const uint32_t function_result = module_.TakeNextId(); |
| const uint32_t void_type = type_manager_.GetTypeVoid().Id(); |
| block.CreateInstruction(spv::OpFunctionCall, {void_type, function_result, link_function_id_, stage_info_id}, inst_it); |
| } |
| |
| // This is a hard thing to fully get right, as things like OpTerminateInvocation/OpKill can end the invocation, but not the shader. |
| // The main thing we are trying to find here is if this is our last chance to report an error message |
| bool LogErrorPass::IsShaderExiting(const Instruction& inst) const { |
| return IsValueIn((spv::Op)inst.Opcode(), {spv::OpReturn, spv::OpReturnValue, spv::OpEmitMeshTasksEXT}); |
| } |
| |
| bool LogErrorPass::Instrument() { |
| for (Function& function : module_.functions_) { |
| // Only need to clear when we know we are starting a new entry into the shader |
| if (function.id_ != module_.target_entry_point_id_) { |
| continue; |
| } |
| |
| // This makes (a reasonably safe) assumption one entrypoint is not calling into another entrypoint after it has caused an |
| // error. If we really care, we could build CFA to detect that, but likely not worth the effort currently. |
| ClearErrorPayloadVariable(function); |
| |
| for (auto block_it = function.blocks_.begin(); block_it != function.blocks_.end(); ++block_it) { |
| BasicBlock& current_block = **block_it; |
| |
| auto& block_instructions = current_block.instructions_; |
| for (auto inst_it = block_instructions.begin(); inst_it != block_instructions.end(); ++inst_it) { |
| if (IsShaderExiting(*(inst_it->get()))) { |
| CreateFunctionCallLogError(function, current_block, &inst_it); |
| instrumentations_count_++; |
| } |
| } |
| } |
| } |
| |
| return instrumentations_count_ != 0; |
| } |
| |
| } // namespace spirv |
| } // namespace gpuav |