blob: df32a3e474bc0c6eebac77f4fcb9f7502dcbdc30 [file] [log] [blame]
/* 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