blob: c762af92e774604476188b935e23cb0feb3b14af [file] [log] [blame]
/* 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 "buffer_device_address_pass.h"
#include "module.h"
#include <spirv/unified1/spirv.hpp>
#include <iostream>
#include "generated/instrumentation_buffer_device_address_comp.h"
namespace gpuav {
namespace spirv {
// By appending the LinkInfo, it will attempt at linking stage to add the function.
uint32_t BufferDeviceAddressPass::GetLinkFunctionId() {
static LinkInfo link_info = {instrumentation_buffer_device_address_comp, instrumentation_buffer_device_address_comp_size, 0,
"inst_buffer_device_address"};
if (link_function_id == 0) {
link_function_id = module_.TakeNextId();
link_info.function_id = link_function_id;
module_.link_info_.push_back(link_info);
}
return link_function_id;
}
uint32_t BufferDeviceAddressPass::CreateFunctionCall(BasicBlock& block, InstructionIt* inst_it,
const InjectionData& injection_data) {
// Convert reference pointer to uint64
const uint32_t pointer_id = target_instruction_->Operand(0);
const Type& uint64_type = module_.type_manager_.GetTypeInt(64, 0);
const uint32_t convert_id = module_.TakeNextId();
block.CreateInstruction(spv::OpConvertPtrToU, {uint64_type.Id(), convert_id, pointer_id}, inst_it);
const Constant& length_constant = module_.type_manager_.GetConstantUInt32(type_length_);
const uint32_t opcode = target_instruction_->Opcode();
const Constant& access_opcode = module_.type_manager_.GetConstantUInt32(opcode);
const Constant& alignment_constant = module_.type_manager_.GetConstantUInt32(alignment_literal_);
const uint32_t function_result = module_.TakeNextId();
const uint32_t function_def = GetLinkFunctionId();
const uint32_t bool_type = module_.type_manager_.GetTypeBool().Id();
block.CreateInstruction(
spv::OpFunctionCall,
{bool_type, function_result, function_def, injection_data.inst_position_id, injection_data.stage_info_id, convert_id,
length_constant.Id(), access_opcode.Id(), alignment_constant.Id()},
inst_it);
return function_result;
}
void BufferDeviceAddressPass::Reset() {
target_instruction_ = nullptr;
alignment_literal_ = 0;
type_length_ = 0;
}
bool BufferDeviceAddressPass::RequiresInstrumentation(const Function& function, const Instruction& inst) {
const uint32_t opcode = inst.Opcode();
if (opcode == spv::OpLoad || opcode == spv::OpStore) {
// We only care if there is an Aligned Memory Operands
// VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708 requires there to be an Aligned operand
const uint32_t memory_operand_index = opcode == spv::OpLoad ? 4 : 3;
const uint32_t alignment_word_index = opcode == spv::OpLoad ? 5 : 4; // OpStore is at [4]
if (inst.Length() < alignment_word_index) {
return false;
}
const uint32_t memory_operands = inst.Word(memory_operand_index);
if ((memory_operands & spv::MemoryAccessAlignedMask) == 0) {
return false;
}
// Even if they are other Memory Operands the spec says it is ordered by smallest bit first,
// Luckily |Aligned| is the smallest bit that can have an operand so we know it is here
alignment_literal_ = inst.Word(alignment_word_index);
} else if (opcode == spv::OpAtomicLoad || opcode == spv::OpAtomicStore || opcode == spv::OpAtomicExchange) {
// Atomics are naturally aligned and by setting this to 1, it will always pass the alignment check
alignment_literal_ = 1;
} else {
return false;
}
// TODO - Should have loop to walk Load/Store to the Pointer,
// this case will not cover things such as OpCopyObject or double OpAccessChains
const Instruction* pointer_inst = function.FindInstruction(inst.Operand(0));
if (!pointer_inst || !pointer_inst->IsAccessChain()) {
return false;
}
// Get the OpTypePointer
const Type* op_type_pointer = module_.type_manager_.FindTypeById(pointer_inst->TypeId());
if (!op_type_pointer || op_type_pointer->spv_type_ != SpvType::kPointer) {
return false;
}
// The OpTypePointer's type
uint32_t accessed_type_id = op_type_pointer->inst_.Operand(1);
const Type* accessed_type = module_.type_manager_.FindTypeById(accessed_type_id);
// Most common case we will just spot the access directly using the PhysicalStorageBuffer pointer
if (op_type_pointer->inst_.Operand(0) == spv::StorageClassPhysicalStorageBuffer) {
// If loading the struct, this is likely just saving it
// Shown from RADV/Intel NIR compiler, the compiler gets an offset and then dereference just the member, it never "loads the
// whole struct"
if (accessed_type->spv_type_ == SpvType::kStruct) {
// If the struct is only a single element, then everything works and the size will be the same
if (accessed_type->inst_.Length() > 3) {
return false;
}
}
} else {
// TODO https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8089
return false;
}
// Save information to be used to make the Function
target_instruction_ = &inst;
type_length_ = module_.type_manager_.TypeLength(*accessed_type);
return true;
}
void BufferDeviceAddressPass::PrintDebugInfo() {
std::cout << "BufferDeviceAddressPass instrumentation count: " << instrumentations_count_ << '\n';
}
} // namespace spirv
} // namespace gpuav