instrument: Use Import linkage for instrumentation functions (#5355)

These functions are getting far too complicated to code in SPIRV-Tools
C++. Replace them with import stubs so that the real implementations
can live in Vulkan-ValidationLayers where they belong.

VVL will need to define these functions in spirv and link them to the
instrumented version of the user's shader.

From here on out, VVL can redefine the functions and any data they use
without updating SPIRV-Tools. Changing the function declarations will
still require both VVL and SPIRV-Tools to be updated in lock step.
This commit is contained in:
Jeremy Gebben 2023-09-20 10:50:30 -06:00 committed by GitHub
parent a40483d313
commit ee7598d497
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2691 additions and 4279 deletions

View File

@ -133,71 +133,6 @@ static const int kInstTaskOutGlobalInvocationIdZ = kInstCommonOutCnt + 2;
// Size of Common and Stage-specific Members
static const int kInstStageOutCnt = kInstCommonOutCnt + 3;
// Validation Error Code Offset
//
// This identifies the validation error. It also helps to identify
// how many words follow in the record and their meaning.
static const int kInstValidationOutError = kInstStageOutCnt;
// Validation-specific Output Record Offsets
//
// Each different validation will generate a potentially different
// number of words at the end of the record giving more specifics
// about the validation error.
//
// A bindless bounds error will output the index and the bound.
static const int kInstBindlessBoundsOutDescSet = kInstStageOutCnt + 1;
static const int kInstBindlessBoundsOutDescBinding = kInstStageOutCnt + 2;
static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 3;
static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 4;
static const int kInstBindlessBoundsOutUnused = kInstStageOutCnt + 5;
static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 6;
// A descriptor uninitialized error will output the index.
static const int kInstBindlessUninitOutDescSet = kInstStageOutCnt + 1;
static const int kInstBindlessUninitOutBinding = kInstStageOutCnt + 2;
static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 3;
static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 4;
static const int kInstBindlessUninitOutUnused2 = kInstStageOutCnt + 5;
static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 6;
// A buffer out-of-bounds error will output the descriptor
// index, the buffer offset and the buffer size
static const int kInstBindlessBuffOOBOutDescSet = kInstStageOutCnt + 1;
static const int kInstBindlessBuffOOBOutDescBinding = kInstStageOutCnt + 2;
static const int kInstBindlessBuffOOBOutDescIndex = kInstStageOutCnt + 3;
static const int kInstBindlessBuffOOBOutBuffOff = kInstStageOutCnt + 4;
static const int kInstBindlessBuffOOBOutBuffSize = kInstStageOutCnt + 5;
static const int kInstBindlessBuffOOBOutCnt = kInstStageOutCnt + 6;
// A buffer address unalloc error will output the 64-bit pointer in
// two 32-bit pieces, lower bits first.
static const int kInstBuffAddrUnallocOutDescPtrLo = kInstStageOutCnt + 1;
static const int kInstBuffAddrUnallocOutDescPtrHi = kInstStageOutCnt + 2;
static const int kInstBuffAddrUnallocOutCnt = kInstStageOutCnt + 3;
// Maximum Output Record Member Count
static const int kInstMaxOutCnt = kInstStageOutCnt + 6;
// Validation Error Codes
//
// These are the possible validation error codes.
static const int kInstErrorBindlessBounds = 1;
static const int kInstErrorBindlessUninit = 2;
static const int kInstErrorBuffAddrUnallocRef = 3;
static const int kInstErrorOOB = 4;
static const int kInstErrorMax = kInstErrorOOB;
// Direct Input Buffer Offsets
//
// The following values provide member offsets into the input buffers
// consumed by InstrumentPass::GenDebugDirectRead(). This method is utilized
// by InstBindlessCheckPass.
//
// The only object in an input buffer is a runtime array of unsigned
// integers. Each validation will have its own formatting of this array.
static const int kDebugInputDataOffset = 0;
// Debug Buffer Bindings
//
// These are the bindings for the different buffers which are
@ -216,63 +151,6 @@ static const int kDebugInputBindingBuffAddr = 2;
// This is the output buffer written by InstDebugPrintfPass.
static const int kDebugOutputPrintfStream = 3;
// clang-format off
// Bindless Validation Input Buffer Format
//
// An input buffer for bindless validation has this structure:
// GLSL:
// layout(buffer_reference, std430, buffer_reference_align = 8) buffer DescriptorSetData {
// uint num_bindings;
// uint data[];
// };
//
// layout(set = 7, binding = 1, std430) buffer inst_bindless_InputBuffer
// {
// DescriptorSetData desc_sets[32];
// } inst_bindless_input_buffer;
//
//
// To look up the length of a binding:
// uint length = inst_bindless_input_buffer[set].data[binding];
// Scalar bindings have a length of 1.
//
// To look up the initialization state of a descriptor in a binding:
// uint num_bindings = inst_bindless_input_buffer[set].num_bindings;
// uint binding_state_start = inst_bindless_input_buffer[set].data[num_bindings + binding];
// uint init_state = inst_bindless_input_buffer[set].data[binding_state_start + index];
//
// For scalar bindings, use 0 for the index.
// clang-format on
//
// The size of the inst_bindless_input_buffer array, regardless of how many
// descriptor sets the device supports.
static const int kDebugInputBindlessMaxDescSets = 32;
// Buffer Device Address Input Buffer Format
//
// An input buffer for buffer device address validation consists of a single
// array of unsigned 64-bit integers we will call Data[]. This array is
// formatted as follows:
//
// At offset kDebugInputBuffAddrPtrOffset is a list of sorted valid buffer
// addresses. The list is terminated with the address 0xffffffffffffffff.
// If 0x0 is not a valid buffer address, this address is inserted at the
// start of the list.
//
static const int kDebugInputBuffAddrPtrOffset = 1;
//
// At offset kDebugInputBuffAddrLengthOffset in Data[] is a single uint64 which
// gives an offset to the start of the buffer length data. More
// specifically, for a buffer whose pointer is located at input buffer offset
// i, the length is located at:
//
// Data[ i - kDebugInputBuffAddrPtrOffset
// + Data[ kDebugInputBuffAddrLengthOffset ] ]
//
// The length associated with the 0xffffffffffffffff address is zero. If
// not a valid buffer, the length associated with the 0x0 address is zero.
static const int kDebugInputBuffAddrLengthOffset = 0;
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_

View File

@ -766,11 +766,9 @@ Optimizer::PassToken CreateCombineAccessChainsPass();
// potentially de-optimizing the instrument code, for example, inlining
// the debug record output function throughout the module.
//
// The instrumentation will read and write buffers in debug
// descriptor set |desc_set|. It will write |shader_id| in each output record
// The instrumentation will write |shader_id| in each output record
// to identify the shader module which generated the record.
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
uint32_t shader_id);
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t shader_id);
// Create a pass to instrument physical buffer address checking
// This pass instruments all physical buffer address references to check that
@ -791,8 +789,7 @@ Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
// The instrumentation will read and 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 CreateInstBuffAddrCheckPass(uint32_t desc_set,
uint32_t shader_id);
Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t shader_id);
// Create a pass to instrument OpDebugPrintf instructions.
// This pass replaces all OpDebugPrintf instructions with instructions to write

View File

@ -39,149 +39,11 @@ constexpr int kSpvTypeImageArrayed = 3;
constexpr int kSpvTypeImageMS = 4;
} // namespace
void InstBindlessCheckPass::SetupInputBufferIds() {
if (input_buffer_id_ != 0) {
return;
}
AddStorageBufferExt();
if (!get_feature_mgr()->HasExtension(kSPV_KHR_physical_storage_buffer)) {
context()->AddExtension("SPV_KHR_physical_storage_buffer");
}
context()->AddCapability(spv::Capability::PhysicalStorageBufferAddresses);
Instruction* memory_model = get_module()->GetMemoryModel();
// TODO should this be just Physical64?
memory_model->SetInOperand(
0u, {uint32_t(spv::AddressingModel::PhysicalStorageBuffer64)});
analysis::DecorationManager* deco_mgr = get_decoration_mgr();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
constexpr uint32_t width = 32u;
// declare the DescriptorSetData struct
analysis::Struct* desc_set_struct =
GetStruct({type_mgr->GetUIntType(), GetUintRuntimeArrayType(width)});
desc_set_type_id_ = type_mgr->GetTypeInstruction(desc_set_struct);
// 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(desc_set_type_id_) == 0 &&
"used struct type returned");
deco_mgr->AddDecoration(desc_set_type_id_, uint32_t(spv::Decoration::Block));
deco_mgr->AddMemberDecoration(desc_set_type_id_, 0,
uint32_t(spv::Decoration::Offset), 0);
deco_mgr->AddMemberDecoration(desc_set_type_id_, 1,
uint32_t(spv::Decoration::Offset), 4);
context()->AddDebug2Inst(
NewGlobalName(desc_set_type_id_, "DescriptorSetData"));
context()->AddDebug2Inst(NewMemberName(desc_set_type_id_, 0, "num_bindings"));
context()->AddDebug2Inst(NewMemberName(desc_set_type_id_, 1, "data"));
// declare buffer address reference to DescriptorSetData
desc_set_ptr_id_ = type_mgr->FindPointerToType(
desc_set_type_id_, spv::StorageClass::PhysicalStorageBuffer);
// runtime array of buffer addresses
analysis::Type* rarr_ty = GetArray(type_mgr->GetType(desc_set_ptr_id_),
kDebugInputBindlessMaxDescSets);
deco_mgr->AddDecorationVal(type_mgr->GetId(rarr_ty),
uint32_t(spv::Decoration::ArrayStride), 8u);
// declare the InputBuffer type, a struct wrapper around the runtime array
analysis::Struct* input_buffer_struct = GetStruct({rarr_ty});
input_buffer_struct_id_ = type_mgr->GetTypeInstruction(input_buffer_struct);
deco_mgr->AddDecoration(input_buffer_struct_id_,
uint32_t(spv::Decoration::Block));
deco_mgr->AddMemberDecoration(input_buffer_struct_id_, 0,
uint32_t(spv::Decoration::Offset), 0);
context()->AddDebug2Inst(
NewGlobalName(input_buffer_struct_id_, "InputBuffer"));
context()->AddDebug2Inst(
NewMemberName(input_buffer_struct_id_, 0, "desc_sets"));
input_buffer_ptr_id_ = type_mgr->FindPointerToType(
input_buffer_struct_id_, spv::StorageClass::StorageBuffer);
// declare the input_buffer global variable
input_buffer_id_ = TakeNextId();
const std::vector<Operand> var_operands = {
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{uint32_t(spv::StorageClass::StorageBuffer)}},
};
auto new_var_op = spvtools::MakeUnique<Instruction>(
context(), spv::Op::OpVariable, input_buffer_ptr_id_, input_buffer_id_,
var_operands);
context()->AddGlobalValue(std::move(new_var_op));
context()->AddDebug2Inst(NewGlobalName(input_buffer_id_, "input_buffer"));
deco_mgr->AddDecorationVal(
input_buffer_id_, uint32_t(spv::Decoration::DescriptorSet), desc_set_);
deco_mgr->AddDecorationVal(input_buffer_id_,
uint32_t(spv::Decoration::Binding),
GetInputBufferBinding());
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, {input_buffer_id_}});
context()->AnalyzeUses(&entry);
}
}
}
// This is a stub function for use with Import linkage
// clang-format off
// GLSL:
//bool inst_bindless_check_desc(uint shader_id, uint inst_num, uvec4 stage_info, uint desc_set, uint binding, uint desc_index,
// uint byte_offset)
//{
// uint error = 0u;
// uint param5 = 0u;
// uint param6 = 0u;
// uint num_bindings = 0u;
// uint init_state = 0u;
// if (desc_set >= 32u) {
// error = 1u;
// }
// inst_bindless_DescriptorSetData set_data;
// if (error == 0u) {
// set_data = inst_bindless_input_buffer.desc_sets[desc_set];
// uvec2 ptr_vec = uvec2(set_data);
// if ((ptr_vec.x == 0u) && (ptr_vec.y == 0u)) {
// error = 1u;
// }
// }
// if (error == 0u) {
// num_bindings = set_data.num_bindings;
// if (binding >= num_bindings) {
// error = 1u;
// }
// }
// if (error == 0u) {
// if (desc_index >= set_data.data[binding]) {
// error = 1u;
// param5 = set_data.data[binding];
// }
// }
// if (0u == error) {
// uint state_index = set_data.data[num_bindings + binding] + desc_index;
// init_state = set_data.data[state_index];
// if (init_state == 0u) {
// error = 2u;
// }
// }
// if (error == 0u) {
// if (byte_offset >= init_state) {
// error = 4u;
// param5 = byte_offset;
// param6 = init_state;
// }
// }
// if (0u != error) {
// inst_bindless_stream_write_6(shader_id, inst_num, stage_info, error, desc_set, binding, desc_index, param5, param6);
// return false;
// }
// return true;
//bool inst_bindless_check_desc(const uint shader_id, const uint inst_num, const uvec4 stage_info, const uint desc_set,
// const uint binding, const uint desc_index, const uint byte_offset) {
//}
// clang-format on
uint32_t InstBindlessCheckPass::GenDescCheckFunctionId() {
@ -195,11 +57,10 @@ uint32_t InstBindlessCheckPass::GenDescCheckFunctionId() {
kByteOffset = 6,
kNumArgs
};
if (desc_check_func_id_ != 0) {
return desc_check_func_id_;
if (check_desc_func_id_ != 0) {
return check_desc_func_id_;
}
SetupInputBufferIds();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
const analysis::Integer* uint_type = GetInteger(32, false);
const analysis::Vector v4uint(uint_type, 4);
@ -211,454 +72,32 @@ uint32_t InstBindlessCheckPass::GenDescCheckFunctionId() {
std::unique_ptr<Function> func =
StartFunction(func_id, type_mgr->GetBoolType(), param_types);
const std::vector<uint32_t> param_ids = AddParameters(*func, param_types);
const uint32_t func_uint_ptr =
type_mgr->FindPointerToType(GetUintId(), spv::StorageClass::Function);
// Create block
auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
InstructionBuilder builder(
context(), new_blk_ptr.get(),
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
Instruction* inst;
const uint32_t zero_id = builder.GetUintConstantId(0);
const uint32_t false_id = builder.GetBoolConstantId(false);
const uint32_t true_id = builder.GetBoolConstantId(true);
const uint32_t uint_ptr = type_mgr->FindPointerToType(
GetUintId(), spv::StorageClass::PhysicalStorageBuffer);
inst = builder.AddBinaryOp(func_uint_ptr, spv::Op::OpVariable,
uint32_t(spv::StorageClass::Function), zero_id);
const uint32_t error_var = inst->result_id();
inst = builder.AddBinaryOp(func_uint_ptr, spv::Op::OpVariable,
uint32_t(spv::StorageClass::Function), zero_id);
const uint32_t param5_var = inst->result_id();
inst = builder.AddBinaryOp(func_uint_ptr, spv::Op::OpVariable,
uint32_t(spv::StorageClass::Function), zero_id);
const uint32_t param6_var = inst->result_id();
inst = builder.AddBinaryOp(func_uint_ptr, spv::Op::OpVariable,
uint32_t(spv::StorageClass::Function), zero_id);
const uint32_t num_bindings_var = inst->result_id();
inst = builder.AddBinaryOp(func_uint_ptr, spv::Op::OpVariable,
uint32_t(spv::StorageClass::Function), zero_id);
const uint32_t init_status_var = inst->result_id();
const uint32_t desc_set_ptr_ptr = type_mgr->FindPointerToType(
desc_set_ptr_id_, spv::StorageClass::Function);
inst = builder.AddUnaryOp(desc_set_ptr_ptr, spv::Op::OpVariable,
uint32_t(spv::StorageClass::Function));
const uint32_t desc_set_ptr_var = inst->result_id();
get_decoration_mgr()->AddDecoration(
desc_set_ptr_var, uint32_t(spv::Decoration::AliasedPointer));
uint32_t check_label_id = TakeNextId();
auto check_label = NewLabel(check_label_id);
uint32_t skip_label_id = TakeNextId();
auto skip_label = NewLabel(skip_label_id);
inst = builder.AddBinaryOp(
GetBoolId(), spv::Op::OpUGreaterThanEqual, param_ids[kDescSet],
builder.GetUintConstantId(kDebugInputBindlessMaxDescSets));
const uint32_t desc_cmp_id = inst->result_id();
(void)builder.AddConditionalBranch(desc_cmp_id, check_label_id, skip_label_id,
skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// set error
new_blk_ptr = MakeUnique<BasicBlock>(std::move(check_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddStore(error_var,
builder.GetUintConstantId(kInstErrorBindlessBounds));
builder.AddBranch(skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// check descriptor set table entry is non-null
new_blk_ptr = MakeUnique<BasicBlock>(std::move(skip_label));
builder.SetInsertPoint(&*new_blk_ptr);
check_label_id = TakeNextId();
check_label = NewLabel(check_label_id);
skip_label_id = TakeNextId();
skip_label = NewLabel(skip_label_id);
inst = builder.AddLoad(GetUintId(), error_var);
uint32_t error_val_id = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, error_val_id,
zero_id);
uint32_t no_error_id = inst->result_id();
(void)builder.AddConditionalBranch(no_error_id, check_label_id, skip_label_id,
skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(check_label));
builder.SetInsertPoint(&*new_blk_ptr);
{
const uint32_t desc_set_ptr_ptr_sb = type_mgr->FindPointerToType(
desc_set_ptr_id_, spv::StorageClass::StorageBuffer);
inst = builder.AddAccessChain(desc_set_ptr_ptr_sb, input_buffer_id_,
{zero_id, param_ids[kDescSet]});
const uint32_t set_access_chain_id = inst->result_id();
inst = builder.AddLoad(desc_set_ptr_id_, set_access_chain_id);
const uint32_t desc_set_ptr_id = inst->result_id();
builder.AddStore(desc_set_ptr_var, desc_set_ptr_id);
inst = builder.AddUnaryOp(GetVecUintId(2), spv::Op::OpBitcast,
desc_set_ptr_id);
const uint32_t ptr_as_uvec_id = inst->result_id();
inst = builder.AddCompositeExtract(GetUintId(), ptr_as_uvec_id, {0});
const uint32_t uvec_x = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, uvec_x, zero_id);
const uint32_t x_is_zero_id = inst->result_id();
inst = builder.AddCompositeExtract(GetUintId(), ptr_as_uvec_id, {1});
const uint32_t uvec_y = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, uvec_y, zero_id);
const uint32_t y_is_zero_id = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpLogicalAnd, x_is_zero_id,
y_is_zero_id);
const uint32_t is_null_id = inst->result_id();
const uint32_t error_label_id = TakeNextId();
auto error_label = NewLabel(error_label_id);
const uint32_t merge_label_id = TakeNextId();
auto merge_label = NewLabel(merge_label_id);
(void)builder.AddConditionalBranch(is_null_id, error_label_id,
merge_label_id, merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// set error
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddStore(error_var,
builder.GetUintConstantId(kInstErrorBindlessBounds));
builder.AddBranch(merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddBranch(skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
}
new_blk_ptr = MakeUnique<BasicBlock>(std::move(skip_label));
builder.SetInsertPoint(&*new_blk_ptr);
check_label_id = TakeNextId();
check_label = NewLabel(check_label_id);
skip_label_id = TakeNextId();
skip_label = NewLabel(skip_label_id);
inst = builder.AddLoad(GetUintId(), error_var);
error_val_id = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, error_val_id,
zero_id);
no_error_id = inst->result_id();
(void)builder.AddConditionalBranch(no_error_id, check_label_id, skip_label_id,
skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// check binding is in range
new_blk_ptr = MakeUnique<BasicBlock>(std::move(check_label));
builder.SetInsertPoint(&*new_blk_ptr);
{
inst = builder.AddLoad(desc_set_ptr_id_, desc_set_ptr_var);
const uint32_t desc_set_ptr_id = inst->result_id();
inst = builder.AddAccessChain(uint_ptr, desc_set_ptr_id, {zero_id});
const uint32_t binding_access_chain_id = inst->result_id();
inst = builder.AddLoad(GetUintId(), binding_access_chain_id, 8);
const uint32_t num_bindings_id = inst->result_id();
builder.AddStore(num_bindings_var, num_bindings_id);
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
param_ids[kDescBinding], num_bindings_id);
const uint32_t bindings_cmp_id = inst->result_id();
const uint32_t error_label_id = TakeNextId();
auto error_label = NewLabel(error_label_id);
const uint32_t merge_label_id = TakeNextId();
auto merge_label = NewLabel(merge_label_id);
(void)builder.AddConditionalBranch(bindings_cmp_id, error_label_id,
merge_label_id, merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// set error
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddStore(error_var,
builder.GetUintConstantId(kInstErrorBindlessBounds));
builder.AddBranch(merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddBranch(skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
}
// read binding length
new_blk_ptr = MakeUnique<BasicBlock>(std::move(skip_label));
builder.SetInsertPoint(&*new_blk_ptr);
check_label_id = TakeNextId();
check_label = NewLabel(check_label_id);
skip_label_id = TakeNextId();
skip_label = NewLabel(skip_label_id);
inst = builder.AddLoad(GetUintId(), error_var);
error_val_id = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, error_val_id,
zero_id);
no_error_id = inst->result_id();
(void)builder.AddConditionalBranch(no_error_id, check_label_id, skip_label_id,
skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(check_label));
builder.SetInsertPoint(&*new_blk_ptr);
{
inst = builder.AddLoad(desc_set_ptr_id_, desc_set_ptr_var);
const uint32_t desc_set_ptr_id = inst->result_id();
inst = builder.AddAccessChain(
uint_ptr, desc_set_ptr_id,
{{builder.GetUintConstantId(1), param_ids[kDescBinding]}});
const uint32_t length_ac_id = inst->result_id();
inst = builder.AddLoad(GetUintId(), length_ac_id, sizeof(uint32_t));
const uint32_t length_id = inst->result_id();
// Check descriptor index in bounds
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
param_ids[kDescIndex], length_id);
const uint32_t desc_idx_range_id = inst->result_id();
const uint32_t error_label_id = TakeNextId();
auto error_label = NewLabel(error_label_id);
const uint32_t merge_label_id = TakeNextId();
auto merge_label = NewLabel(merge_label_id);
(void)builder.AddConditionalBranch(desc_idx_range_id, error_label_id,
merge_label_id, merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// set error
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddStore(error_var,
builder.GetUintConstantId(kInstErrorBindlessBounds));
builder.AddStore(param5_var, length_id);
builder.AddBranch(merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddBranch(skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
}
new_blk_ptr = MakeUnique<BasicBlock>(std::move(skip_label));
builder.SetInsertPoint(&*new_blk_ptr);
inst = builder.AddLoad(GetUintId(), error_var);
error_val_id = inst->result_id();
check_label_id = TakeNextId();
check_label = NewLabel(check_label_id);
skip_label_id = TakeNextId();
skip_label = NewLabel(skip_label_id);
inst = builder.AddLoad(GetUintId(), error_var);
error_val_id = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, zero_id,
error_val_id);
no_error_id = inst->result_id();
(void)builder.AddConditionalBranch(no_error_id, check_label_id, skip_label_id,
skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// Read descriptor init status
new_blk_ptr = MakeUnique<BasicBlock>(std::move(check_label));
builder.SetInsertPoint(&*new_blk_ptr);
{
inst = builder.AddLoad(desc_set_ptr_id_, desc_set_ptr_var);
const uint32_t desc_set_ptr_id = inst->result_id();
inst = builder.AddLoad(GetUintId(), num_bindings_var);
const uint32_t num_bindings_id = inst->result_id();
inst =
builder.AddIAdd(GetUintId(), num_bindings_id, param_ids[kDescBinding]);
const uint32_t state_offset_id = inst->result_id();
inst = builder.AddAccessChain(
uint_ptr, desc_set_ptr_id,
{{builder.GetUintConstantId(1), state_offset_id}});
const uint32_t state_start_ac_id = inst->result_id();
inst = builder.AddLoad(GetUintId(), state_start_ac_id, sizeof(uint32_t));
const uint32_t state_start_id = inst->result_id();
inst = builder.AddIAdd(GetUintId(), state_start_id, param_ids[kDescIndex]);
const uint32_t state_entry_id = inst->result_id();
// Note: length starts from the beginning of the buffer, not the beginning
// of the data array
inst = builder.AddAccessChain(
uint_ptr, desc_set_ptr_id,
{{builder.GetUintConstantId(1), state_entry_id}});
const uint32_t init_ac_id = inst->result_id();
inst = builder.AddLoad(GetUintId(), init_ac_id, sizeof(uint32_t));
const uint32_t init_status_id = inst->result_id();
builder.AddStore(init_status_var, init_status_id);
// Check for uninitialized descriptor
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, init_status_id,
zero_id);
const uint32_t uninit_check_id = inst->result_id();
const uint32_t error_label_id = TakeNextId();
auto error_label = NewLabel(error_label_id);
const uint32_t merge_label_id = TakeNextId();
auto merge_label = NewLabel(merge_label_id);
(void)builder.AddConditionalBranch(uninit_check_id, error_label_id,
merge_label_id, merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddStore(error_var,
builder.GetUintConstantId(kInstErrorBindlessUninit));
builder.AddBranch(merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddBranch(skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
}
// Check for OOB.
new_blk_ptr = MakeUnique<BasicBlock>(std::move(skip_label));
builder.SetInsertPoint(&*new_blk_ptr);
check_label_id = TakeNextId();
check_label = NewLabel(check_label_id);
skip_label_id = TakeNextId();
skip_label = NewLabel(skip_label_id);
inst = builder.AddLoad(GetUintId(), error_var);
error_val_id = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, error_val_id,
zero_id);
no_error_id = inst->result_id();
(void)builder.AddConditionalBranch(no_error_id, check_label_id, skip_label_id,
skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(check_label));
builder.SetInsertPoint(&*new_blk_ptr);
{
inst = builder.AddLoad(GetUintId(), init_status_var);
const uint32_t init_status_id = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
param_ids[kByteOffset], init_status_id);
const uint32_t buf_offset_range_id = inst->result_id();
const uint32_t error_label_id = TakeNextId();
const uint32_t merge_label_id = TakeNextId();
auto error_label = NewLabel(error_label_id);
auto merge_label = NewLabel(merge_label_id);
(void)builder.AddConditionalBranch(buf_offset_range_id, error_label_id,
merge_label_id, merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// set error
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddStore(error_var, builder.GetUintConstantId(kInstErrorOOB));
builder.AddStore(param5_var, param_ids[kByteOffset]);
builder.AddStore(param6_var, init_status_id);
builder.AddBranch(merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
builder.SetInsertPoint(&*new_blk_ptr);
builder.AddBranch(skip_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
}
// check for error
new_blk_ptr = MakeUnique<BasicBlock>(std::move(skip_label));
builder.SetInsertPoint(&*new_blk_ptr);
inst = builder.AddLoad(GetUintId(), error_var);
error_val_id = inst->result_id();
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpINotEqual, zero_id,
error_val_id);
const uint32_t is_error_id = inst->result_id();
const uint32_t error_label_id = TakeNextId();
auto error_label = NewLabel(error_label_id);
const uint32_t merge_label_id = TakeNextId();
auto merge_label = NewLabel(merge_label_id);
(void)builder.AddConditionalBranch(is_error_id, error_label_id,
merge_label_id, merge_label_id);
func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
builder.SetInsertPoint(&*new_blk_ptr);
// error output
inst = builder.AddLoad(GetUintId(), param5_var);
const uint32_t param5_val_id = inst->result_id();
inst = builder.AddLoad(GetUintId(), param6_var);
const uint32_t param6_val_id = inst->result_id();
GenDebugStreamWrite(
param_ids[kShaderId], param_ids[kInstructionIndex], param_ids[kStageInfo],
{error_val_id, param_ids[kDescSet], param_ids[kDescBinding],
param_ids[kDescIndex], param5_val_id, param6_val_id},
&builder);
(void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, false_id);
func->AddBasicBlock(std::move(new_blk_ptr));
// Success return
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
builder.SetInsertPoint(&*new_blk_ptr);
(void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, true_id);
func->AddBasicBlock(std::move(new_blk_ptr));
func->SetFunctionEnd(EndFunction());
context()->AddFunction(std::move(func));
context()->AddDebug2Inst(NewGlobalName(func_id, "desc_check"));
static const std::string func_name{"inst_bindless_check_desc"};
context()->AddFunctionDeclaration(std::move(func));
context()->AddDebug2Inst(NewName(func_id, func_name));
std::vector<Operand> operands{
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {func_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{uint32_t(spv::Decoration::LinkageAttributes)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_STRING,
utils::MakeVector(func_name.c_str())},
{spv_operand_type_t::SPV_OPERAND_TYPE_LINKAGE_TYPE,
{uint32_t(spv::LinkageType::Import)}},
};
get_decoration_mgr()->AddDecoration(spv::Op::OpDecorate, operands);
desc_check_func_id_ = func_id;
check_desc_func_id_ = func_id;
// Make sure function doesn't get processed by
// InstrumentPass::InstProcessCallTreeFromRoots()
param2output_func_id_[3] = func_id;
return desc_check_func_id_;
return check_desc_func_id_;
}
// clang-format off
// GLSL:
// result = inst_bindless_desc_check(shader_id, inst_idx, stage_info, desc_set, binding, desc_idx, offset);
// result = inst_bindless_check_desc(shader_id, inst_idx, stage_info, desc_set, binding, desc_idx, offset);
//
// clang-format on
uint32_t InstBindlessCheckPass::GenDescCheckCall(
@ -1134,8 +573,7 @@ uint32_t InstBindlessCheckPass::GenLastByteIdx(RefAnalysis* ref,
}
void InstBindlessCheckPass::GenCheckCode(
uint32_t check_id, uint32_t error_id, uint32_t offset_id,
uint32_t length_id, uint32_t stage_idx, RefAnalysis* ref,
uint32_t check_id, RefAnalysis* ref,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
BasicBlock* back_blk_ptr = &*new_blocks->back();
InstructionBuilder builder(
@ -1164,31 +602,7 @@ void InstBindlessCheckPass::GenCheckCode(
// Gen invalid block
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
if (error_id != 0) {
const uint32_t u_shader_id = builder.GetUintConstantId(shader_id_);
const uint32_t u_inst_id =
builder.GetUintConstantId(ref->ref_inst->unique_id());
const uint32_t shader_info_id = GenStageInfo(stage_idx, &builder);
const uint32_t u_set_id = builder.GetUintConstantId(ref->set);
const uint32_t u_binding_id = builder.GetUintConstantId(ref->binding);
const uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder);
const uint32_t u_length_id = GenUintCastCode(length_id, &builder);
if (offset_id != 0) {
const uint32_t u_offset_id = GenUintCastCode(offset_id, &builder);
// Buffer OOB
GenDebugStreamWrite(u_shader_id, u_inst_id, shader_info_id,
{error_id, u_set_id, u_binding_id, u_index_id,
u_offset_id, u_length_id},
&builder);
} else {
// Uninitialized Descriptor - Return additional unused zero so all error
// modes will use same debug stream write function
GenDebugStreamWrite(u_shader_id, u_inst_id, shader_info_id,
{error_id, u_set_id, u_binding_id, u_index_id,
u_length_id, builder.GetUintConstantId(0)},
&builder);
}
}
// Generate a ConstantNull, converting to uint64 if the type cannot be a null.
if (new_ref_id != 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
@ -1283,7 +697,7 @@ void InstBindlessCheckPass::GenDescCheckCode(
// Generate runtime initialization/bounds test code with true branch
// being full reference and false branch being zero
// for the referenced value.
GenCheckCode(check_id, 0, 0, 0, stage_idx, &ref, new_blocks);
GenCheckCode(check_id, &ref, new_blocks);
// Move original block's remaining code into remainder/merge block and add
// to new blocks
@ -1311,6 +725,20 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() {
Pass::Status InstBindlessCheckPass::ProcessImpl() {
bool modified = false;
// The memory model and linkage must always be updated for spirv-link to work
// correctly.
AddStorageBufferExt();
if (!get_feature_mgr()->HasExtension(kSPV_KHR_physical_storage_buffer)) {
context()->AddExtension("SPV_KHR_physical_storage_buffer");
}
context()->AddCapability(spv::Capability::PhysicalStorageBufferAddresses);
Instruction* memory_model = get_module()->GetMemoryModel();
memory_model->SetInOperand(
0u, {uint32_t(spv::AddressingModel::PhysicalStorageBuffer64)});
context()->AddCapability(spv::Capability::Linkage);
InstProcessFunction pfn =
[this](BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,

View File

@ -28,8 +28,8 @@ namespace opt {
// external design may change as the layer evolves.
class InstBindlessCheckPass : public InstrumentPass {
public:
InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
: InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, true) {}
InstBindlessCheckPass(uint32_t shader_id)
: InstrumentPass(0, shader_id, true) {}
~InstBindlessCheckPass() override = default;
@ -44,8 +44,6 @@ class InstBindlessCheckPass : public InstrumentPass {
uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
void SetupInputBufferIds();
uint32_t GenDescCheckFunctionId();
uint32_t GenDescCheckCall(uint32_t inst_idx, uint32_t stage_idx,
@ -107,8 +105,7 @@ class InstBindlessCheckPass : public InstrumentPass {
// writes debug error output utilizing |ref|, |error_id|, |length_id| and
// |stage_idx|. Generate merge block for valid and invalid branches. Kill
// original reference.
void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t offset_id,
uint32_t length_id, uint32_t stage_idx, RefAnalysis* ref,
void GenCheckCode(uint32_t check_id, RefAnalysis* ref,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
// Initialize state for instrumenting bindless checking
@ -124,11 +121,7 @@ class InstBindlessCheckPass : public InstrumentPass {
// Mapping from variable to binding
std::unordered_map<uint32_t, uint32_t> var2binding_;
uint32_t desc_check_func_id_{0};
uint32_t desc_set_type_id_{0};
uint32_t desc_set_ptr_id_{0};
uint32_t input_buffer_struct_id_{0};
uint32_t input_buffer_ptr_id_{0};
uint32_t check_desc_func_id_{0};
};
} // namespace opt

View File

@ -19,24 +19,6 @@
namespace spvtools {
namespace opt {
bool InstBuffAddrCheckPass::InstrumentFunction(Function* func,
uint32_t stage_idx,
InstProcessFunction& pfn) {
// The bindless instrumentation pass adds functions that use
// BufferDeviceAddress They should not be instrumented by this pass.
Instruction* func_name_inst =
context()->GetNames(func->DefInst().result_id()).begin()->second;
if (func_name_inst) {
static const std::string kPrefix{"inst_bindless_"};
std::string func_name = func_name_inst->GetOperand(1).AsString();
if (func_name.size() >= kPrefix.size() &&
func_name.compare(0, kPrefix.size(), kPrefix) == 0) {
return false;
}
}
return InstrumentPass::InstrumentFunction(func, stage_idx, pfn);
}
uint32_t InstBuffAddrCheckPass::CloneOriginalReference(
Instruction* ref_inst, InstructionBuilder* builder) {
// Clone original ref with new result id (if load)
@ -76,8 +58,7 @@ bool InstBuffAddrCheckPass::IsPhysicalBuffAddrReference(Instruction* ref_inst) {
// TODO(greg-lunarg): Refactor with InstBindlessCheckPass::GenCheckCode() ??
void InstBuffAddrCheckPass::GenCheckCode(
uint32_t check_id, uint32_t error_id, uint32_t ref_uptr_id,
uint32_t stage_idx, Instruction* ref_inst,
uint32_t check_id, Instruction* ref_inst,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
BasicBlock* back_blk_ptr = &*new_blocks->back();
InstructionBuilder builder(
@ -104,20 +85,6 @@ void InstBuffAddrCheckPass::GenCheckCode(
// Gen invalid block
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// Convert uptr from uint64 to 2 uint32
Instruction* lo_uptr_inst =
builder.AddUnaryOp(GetUintId(), spv::Op::OpUConvert, ref_uptr_id);
Instruction* rshift_uptr_inst =
builder.AddBinaryOp(GetUint64Id(), spv::Op::OpShiftRightLogical,
ref_uptr_id, builder.GetUintConstantId(32));
Instruction* hi_uptr_inst = builder.AddUnaryOp(
GetUintId(), spv::Op::OpUConvert, rshift_uptr_inst->result_id());
GenDebugStreamWrite(
builder.GetUintConstantId(shader_id_),
builder.GetUintConstantId(uid2offset_[ref_inst->unique_id()]),
GenStageInfo(stage_idx, &builder),
{error_id, lo_uptr_inst->result_id(), hi_uptr_inst->result_id()},
&builder);
// Gen zero for invalid load. If pointer type, need to convert uint64
// zero to pointer; cannot create ConstantNull of pointer type.
uint32_t null_id = 0;
@ -206,201 +173,86 @@ void InstBuffAddrCheckPass::AddParam(uint32_t type_id,
(*input_func)->AddParameter(std::move(param_inst));
}
// This is a stub function for use with Import linkage
// clang-format off
// GLSL:
//bool inst_bindless_search_and_test(const uint shader_id, const uint inst_num, const uvec4 stage_info,
// const uint64 ref_ptr, const uint length) {
//}
// clang-format on
uint32_t InstBuffAddrCheckPass::GetSearchAndTestFuncId() {
if (search_test_func_id_ == 0) {
// Generate function "bool search_and_test(uint64_t ref_ptr, uint32_t len)"
// which searches input buffer for buffer which most likely contains the
// pointer value |ref_ptr| and verifies that the entire reference of
// length |len| bytes is contained in the buffer.
search_test_func_id_ = TakeNextId();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
std::vector<const analysis::Type*> param_types = {
type_mgr->GetType(GetUint64Id()), type_mgr->GetType(GetUintId())};
analysis::Function func_ty(type_mgr->GetType(GetBoolId()), param_types);
analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
std::unique_ptr<Instruction> func_inst(
new Instruction(get_module()->context(), spv::Op::OpFunction,
GetBoolId(), search_test_func_id_,
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{uint32_t(spv::FunctionControlMask::MaskNone)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{type_mgr->GetTypeInstruction(reg_func_ty)}}}));
get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
std::unique_ptr<Function> input_func =
MakeUnique<Function>(std::move(func_inst));
std::vector<uint32_t> param_vec;
// Add ref_ptr and length parameters
AddParam(GetUint64Id(), &param_vec, &input_func);
AddParam(GetUintId(), &param_vec, &input_func);
// Empty first block.
uint32_t first_blk_id = TakeNextId();
std::unique_ptr<Instruction> first_blk_label(NewLabel(first_blk_id));
std::unique_ptr<BasicBlock> first_blk_ptr =
MakeUnique<BasicBlock>(std::move(first_blk_label));
InstructionBuilder builder(
context(), &*first_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
uint32_t hdr_blk_id = TakeNextId();
// Branch to search loop header
std::unique_ptr<Instruction> hdr_blk_label(NewLabel(hdr_blk_id));
(void)builder.AddBranch(hdr_blk_id);
input_func->AddBasicBlock(std::move(first_blk_ptr));
// Linear search loop header block
// TODO(greg-lunarg): Implement binary search
std::unique_ptr<BasicBlock> hdr_blk_ptr =
MakeUnique<BasicBlock>(std::move(hdr_blk_label));
builder.SetInsertPoint(&*hdr_blk_ptr);
// Phi for search index. Starts with 1.
uint32_t cont_blk_id = TakeNextId();
std::unique_ptr<Instruction> cont_blk_label(NewLabel(cont_blk_id));
// Deal with def-use cycle caused by search loop index computation.
// Create Add and Phi instructions first, then do Def analysis on Add.
// Add Phi and Add instructions and do Use analysis later.
uint32_t idx_phi_id = TakeNextId();
uint32_t idx_inc_id = TakeNextId();
std::unique_ptr<Instruction> idx_inc_inst(new Instruction(
context(), spv::Op::OpIAdd, GetUintId(), idx_inc_id,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {idx_phi_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{builder.GetUintConstantId(1u)}}}));
std::unique_ptr<Instruction> idx_phi_inst(new Instruction(
context(), spv::Op::OpPhi, GetUintId(), idx_phi_id,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{builder.GetUintConstantId(1u)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {first_blk_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {idx_inc_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cont_blk_id}}}));
get_def_use_mgr()->AnalyzeInstDef(&*idx_inc_inst);
// Add (previously created) search index phi
(void)builder.AddInstruction(std::move(idx_phi_inst));
// LoopMerge
uint32_t bound_test_blk_id = TakeNextId();
std::unique_ptr<Instruction> bound_test_blk_label(
NewLabel(bound_test_blk_id));
(void)builder.AddLoopMerge(bound_test_blk_id, cont_blk_id,
uint32_t(spv::LoopControlMask::MaskNone));
// Branch to continue/work block
(void)builder.AddBranch(cont_blk_id);
input_func->AddBasicBlock(std::move(hdr_blk_ptr));
// Continue/Work Block. Read next buffer pointer and break if greater
// than ref_ptr arg.
std::unique_ptr<BasicBlock> cont_blk_ptr =
MakeUnique<BasicBlock>(std::move(cont_blk_label));
builder.SetInsertPoint(&*cont_blk_ptr);
// Add (previously created) search index increment now.
(void)builder.AddInstruction(std::move(idx_inc_inst));
// Load next buffer address from debug input buffer
uint32_t ibuf_id = GetInputBufferId();
uint32_t ibuf_ptr_id = GetInputBufferPtrId();
Instruction* uptr_ac_inst = builder.AddTernaryOp(
ibuf_ptr_id, spv::Op::OpAccessChain, ibuf_id,
builder.GetUintConstantId(kDebugInputDataOffset), idx_inc_id);
uint32_t ibuf_type_id = GetInputBufferTypeId();
Instruction* uptr_load_inst = builder.AddUnaryOp(
ibuf_type_id, spv::Op::OpLoad, uptr_ac_inst->result_id());
// If loaded address greater than ref_ptr arg, break, else branch back to
// loop header
Instruction* uptr_test_inst =
builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThan,
uptr_load_inst->result_id(), param_vec[0]);
(void)builder.AddConditionalBranch(
uptr_test_inst->result_id(), bound_test_blk_id, hdr_blk_id, kInvalidId,
uint32_t(spv::SelectionControlMask::MaskNone));
input_func->AddBasicBlock(std::move(cont_blk_ptr));
// Bounds test block. Read length of selected buffer and test that
// all len arg bytes are in buffer.
std::unique_ptr<BasicBlock> bound_test_blk_ptr =
MakeUnique<BasicBlock>(std::move(bound_test_blk_label));
builder.SetInsertPoint(&*bound_test_blk_ptr);
// Decrement index to point to previous/candidate buffer address
Instruction* cand_idx_inst =
builder.AddBinaryOp(GetUintId(), spv::Op::OpISub, idx_inc_id,
builder.GetUintConstantId(1u));
// Load candidate buffer address
Instruction* cand_ac_inst =
builder.AddTernaryOp(ibuf_ptr_id, spv::Op::OpAccessChain, ibuf_id,
builder.GetUintConstantId(kDebugInputDataOffset),
cand_idx_inst->result_id());
Instruction* cand_load_inst = builder.AddUnaryOp(
ibuf_type_id, spv::Op::OpLoad, cand_ac_inst->result_id());
// Compute offset of ref_ptr from candidate buffer address
Instruction* offset_inst =
builder.AddBinaryOp(ibuf_type_id, spv::Op::OpISub, param_vec[0],
cand_load_inst->result_id());
// Convert ref length to uint64
Instruction* ref_len_64_inst =
builder.AddUnaryOp(ibuf_type_id, spv::Op::OpUConvert, param_vec[1]);
// Add ref length to ref offset to compute end of reference
Instruction* ref_end_inst = builder.AddBinaryOp(
ibuf_type_id, spv::Op::OpIAdd, offset_inst->result_id(),
ref_len_64_inst->result_id());
// Load starting index of lengths in input buffer and convert to uint32
Instruction* len_start_ac_inst =
builder.AddTernaryOp(ibuf_ptr_id, spv::Op::OpAccessChain, ibuf_id,
builder.GetUintConstantId(kDebugInputDataOffset),
builder.GetUintConstantId(0u));
Instruction* len_start_load_inst = builder.AddUnaryOp(
ibuf_type_id, spv::Op::OpLoad, len_start_ac_inst->result_id());
Instruction* len_start_32_inst = builder.AddUnaryOp(
GetUintId(), spv::Op::OpUConvert, len_start_load_inst->result_id());
// Decrement search index to get candidate buffer length index
Instruction* cand_len_idx_inst = builder.AddBinaryOp(
GetUintId(), spv::Op::OpISub, cand_idx_inst->result_id(),
builder.GetUintConstantId(1u));
// Add candidate length index to start index
Instruction* len_idx_inst = builder.AddBinaryOp(
GetUintId(), spv::Op::OpIAdd, cand_len_idx_inst->result_id(),
len_start_32_inst->result_id());
// Load candidate buffer length
Instruction* len_ac_inst =
builder.AddTernaryOp(ibuf_ptr_id, spv::Op::OpAccessChain, ibuf_id,
builder.GetUintConstantId(kDebugInputDataOffset),
len_idx_inst->result_id());
Instruction* len_load_inst = builder.AddUnaryOp(
ibuf_type_id, spv::Op::OpLoad, len_ac_inst->result_id());
// Test if reference end within candidate buffer length
Instruction* len_test_inst = builder.AddBinaryOp(
GetBoolId(), spv::Op::OpULessThanEqual, ref_end_inst->result_id(),
len_load_inst->result_id());
// Return test result
(void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
len_test_inst->result_id());
// Close block
input_func->AddBasicBlock(std::move(bound_test_blk_ptr));
// Close function and add function to module
std::unique_ptr<Instruction> func_end_inst(new Instruction(
get_module()->context(), spv::Op::OpFunctionEnd, 0, 0, {}));
get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
input_func->SetFunctionEnd(std::move(func_end_inst));
context()->AddFunction(std::move(input_func));
context()->AddDebug2Inst(
NewGlobalName(search_test_func_id_, "search_and_test"));
enum {
kShaderId = 0,
kInstructionIndex = 1,
kStageInfo = 2,
kRefPtr = 3,
kLength = 4,
kNumArgs
};
if (search_test_func_id_ != 0) {
return search_test_func_id_;
}
// Generate function "bool search_and_test(uint64_t ref_ptr, uint32_t len)"
// which searches input buffer for buffer which most likely contains the
// pointer value |ref_ptr| and verifies that the entire reference of
// length |len| bytes is contained in the buffer.
analysis::TypeManager* type_mgr = context()->get_type_mgr();
const analysis::Integer* uint_type = GetInteger(32, false);
const analysis::Vector v4uint(uint_type, 4);
const analysis::Type* v4uint_type = type_mgr->GetRegisteredType(&v4uint);
std::vector<const analysis::Type*> param_types = {
uint_type, uint_type, v4uint_type, type_mgr->GetType(GetUint64Id()),
uint_type};
const std::string func_name{"inst_buff_addr_search_and_test"};
const uint32_t func_id = TakeNextId();
std::unique_ptr<Function> func =
StartFunction(func_id, type_mgr->GetBoolType(), param_types);
func->SetFunctionEnd(EndFunction());
context()->AddFunctionDeclaration(std::move(func));
context()->AddDebug2Inst(NewName(func_id, func_name));
std::vector<Operand> operands{
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {func_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{uint32_t(spv::Decoration::LinkageAttributes)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_STRING,
utils::MakeVector(func_name.c_str())},
{spv_operand_type_t::SPV_OPERAND_TYPE_LINKAGE_TYPE,
{uint32_t(spv::LinkageType::Import)}},
};
get_decoration_mgr()->AddDecoration(spv::Op::OpDecorate, operands);
search_test_func_id_ = func_id;
return search_test_func_id_;
}
uint32_t InstBuffAddrCheckPass::GenSearchAndTest(Instruction* ref_inst,
InstructionBuilder* builder,
uint32_t* ref_uptr_id) {
uint32_t* ref_uptr_id,
uint32_t stage_idx) {
// Enable Int64 if necessary
context()->AddCapability(spv::Capability::Int64);
// Convert reference pointer to uint64
uint32_t ref_ptr_id = ref_inst->GetSingleWordInOperand(0);
const uint32_t ref_ptr_id = ref_inst->GetSingleWordInOperand(0);
Instruction* ref_uptr_inst =
builder->AddUnaryOp(GetUint64Id(), spv::Op::OpConvertPtrToU, ref_ptr_id);
*ref_uptr_id = ref_uptr_inst->result_id();
// Compute reference length in bytes
analysis::DefUseManager* du_mgr = get_def_use_mgr();
Instruction* ref_ptr_inst = du_mgr->GetDef(ref_ptr_id);
uint32_t ref_ptr_ty_id = ref_ptr_inst->type_id();
const uint32_t ref_ptr_ty_id = ref_ptr_inst->type_id();
Instruction* ref_ptr_ty_inst = du_mgr->GetDef(ref_ptr_ty_id);
uint32_t ref_len = GetTypeLength(ref_ptr_ty_inst->GetSingleWordInOperand(1));
uint32_t ref_len_id = builder->GetUintConstantId(ref_len);
const uint32_t ref_len =
GetTypeLength(ref_ptr_ty_inst->GetSingleWordInOperand(1));
// Gen call to search and test function
Instruction* call_inst = builder->AddFunctionCall(
GetBoolId(), GetSearchAndTestFuncId(), {*ref_uptr_id, ref_len_id});
uint32_t retval = call_inst->result_id();
return retval;
const uint32_t func_id = GetSearchAndTestFuncId();
const std::vector<uint32_t> args = {
builder->GetUintConstantId(shader_id_),
builder->GetUintConstantId(ref_inst->unique_id()),
GenStageInfo(stage_idx, builder), *ref_uptr_id,
builder->GetUintConstantId(ref_len)};
return GenReadFunctionCall(GetBoolId(), func_id, args, builder);
}
void InstBuffAddrCheckPass::GenBuffAddrCheckCode(
@ -418,16 +270,16 @@ void InstBuffAddrCheckPass::GenBuffAddrCheckCode(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
new_blocks->push_back(std::move(new_blk_ptr));
uint32_t error_id = builder.GetUintConstantId(kInstErrorBuffAddrUnallocRef);
// Generate code to do search and test if all bytes of reference
// are within a listed buffer. Return reference pointer converted to uint64.
uint32_t ref_uptr_id;
uint32_t valid_id = GenSearchAndTest(ref_inst, &builder, &ref_uptr_id);
uint32_t valid_id =
GenSearchAndTest(ref_inst, &builder, &ref_uptr_id, stage_idx);
// Generate test of search results with true branch
// being full reference and false branch being debug output and zero
// for the referenced value.
GenCheckCode(valid_id, error_id, ref_uptr_id, stage_idx, ref_inst,
new_blocks);
GenCheckCode(valid_id, ref_inst, new_blocks);
// Move original block's remaining code into remainder/merge block and add
// to new blocks
BasicBlock* back_blk_ptr = &*new_blocks->back();
@ -442,6 +294,15 @@ void InstBuffAddrCheckPass::InitInstBuffAddrCheck() {
}
Pass::Status InstBuffAddrCheckPass::ProcessImpl() {
// The memory model and linkage must always be updated for spirv-link to work
// correctly.
AddStorageBufferExt();
if (!get_feature_mgr()->HasExtension(kSPV_KHR_physical_storage_buffer)) {
context()->AddExtension("SPV_KHR_physical_storage_buffer");
}
context()->AddCapability(spv::Capability::Int64);
context()->AddCapability(spv::Capability::Linkage);
// Perform bindless bounds check on each entry point function in module
InstProcessFunction pfn =
[this](BasicBlock::iterator ref_inst_itr,

View File

@ -29,10 +29,9 @@ namespace opt {
class InstBuffAddrCheckPass : public InstrumentPass {
public:
// For test harness only
InstBuffAddrCheckPass() : InstrumentPass(7, 23, kInstValidationIdBuffAddr) {}
InstBuffAddrCheckPass() : InstrumentPass(0, 23) {}
// For all other interfaces
InstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id)
: InstrumentPass(desc_set, shader_id, kInstValidationIdBuffAddr) {}
InstBuffAddrCheckPass(uint32_t shader_id) : InstrumentPass(0, shader_id) {}
~InstBuffAddrCheckPass() override = default;
@ -41,9 +40,6 @@ class InstBuffAddrCheckPass : public InstrumentPass {
const char* name() const override { return "inst-buff-addr-check-pass"; }
bool InstrumentFunction(Function* func, uint32_t stage_idx,
InstProcessFunction& pfn) override;
private:
// Return byte length of type |type_id|. Must be int, float, vector, matrix,
// struct, array or physical pointer. Uses std430 alignment and sizes.
@ -61,7 +57,7 @@ class InstBuffAddrCheckPass : public InstrumentPass {
// are within the buffer. Returns id of boolean value which is true if
// search and test is successful, false otherwise.
uint32_t GenSearchAndTest(Instruction* ref_inst, InstructionBuilder* builder,
uint32_t* ref_uptr_id);
uint32_t* ref_uptr_id, uint32_t stage_idx);
// This function does checking instrumentation on a single
// instruction which references through a physical storage buffer address.
@ -114,8 +110,7 @@ class InstBuffAddrCheckPass : public InstrumentPass {
// writes debug error output utilizing |ref_inst|, |error_id| and
// |stage_idx|. Generate merge block for valid and invalid reference blocks.
// Kill original reference.
void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id,
uint32_t stage_idx, Instruction* ref_inst,
void GenCheckCode(uint32_t check_id, Instruction* ref_inst,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
// Initialize state for instrumenting physical buffer address checking

View File

@ -16,6 +16,7 @@
#include "inst_debug_printf_pass.h"
#include "source/spirv_constant.h"
#include "source/util/string_utils.h"
#include "spirv/unified1/NonSemanticDebugPrintf.h"
@ -210,9 +211,244 @@ void InstDebugPrintfPass::GenDebugPrintfCode(
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<Instruction> 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,
kStageInfo = 2,
kFirstParam = 3,
};
// 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);
const analysis::Vector v4uint(uint_type, 4);
const analysis::Type* v4uint_type = type_mgr->GetRegisteredType(&v4uint);
std::vector<const analysis::Type*> param_types(kFirstParam + param_cnt,
uint_type);
param_types[kStageInfo] = v4uint_type;
std::unique_ptr<Function> output_func = StartFunction(
param2output_func_id_[param_cnt], type_mgr->GetVoidType(), param_types);
std::vector<uint32_t> param_ids = AddParameters(*output_func, param_types);
// Create first block
auto new_blk_ptr = MakeUnique<BasicBlock>(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 val_spec_offset = kInstStageOutCnt;
const uint32_t obuf_record_sz = val_spec_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<Instruction> merge_label(NewLabel(merge_blk_id));
std::unique_ptr<Instruction> 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<BasicBlock>(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);
// Store stage info. Stage Idx + 3 words of stage-specific data.
for (uint32_t i = 0; i < 4; ++i) {
Instruction* field =
builder.AddCompositeExtract(GetUintId(), param_ids[kStageInfo], {i});
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutStageIdx + i,
field->result_id(), &builder);
}
// Gen writes of validation specific data
for (uint32_t i = 0; i < param_cnt; ++i) {
GenDebugOutputFieldCode(obuf_curr_sz_id, val_spec_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<BasicBlock>(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 += std::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, uint32_t stage_info_id,
const std::vector<uint32_t>& 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<uint32_t>(validation_ids.size());
std::vector<uint32_t> args = {shader_id, instruction_idx_id, stage_info_id};
(void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
(void)builder->AddFunctionCall(GetVoidId(),
GetStreamWriteFunctionId(val_id_cnt), args);
}
std::unique_ptr<Instruction> 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<Instruction> InstDebugPrintfPass::NewMemberName(
uint32_t id, uint32_t member_index, const std::string& name_str) {
return MakeUnique<Instruction>(
context(), spv::Op::OpMemberName, 0, 0,
std::initializer_list<Operand>{
{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() {

View File

@ -28,10 +28,10 @@ namespace opt {
class InstDebugPrintfPass : public InstrumentPass {
public:
// For test harness only
InstDebugPrintfPass() : InstrumentPass(7, 23, kInstValidationIdDebugPrintf) {}
InstDebugPrintfPass() : InstrumentPass(7, 23) {}
// For all other interfaces
InstDebugPrintfPass(uint32_t desc_set, uint32_t shader_id)
: InstrumentPass(desc_set, shader_id, kInstValidationIdDebugPrintf) {}
: InstrumentPass(desc_set, shader_id) {}
~InstDebugPrintfPass() override = default;
@ -41,6 +41,104 @@ class InstDebugPrintfPass : public InstrumentPass {
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. It will also contain additional information to
// identify the instance of the shader, depending on the stage |stage_idx|
// of the shader. 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
// Stage
// Stage-specific Word 0
// Stage-specific Word 1
// ...
// Validation Error Code
// Validation-specific Word 0
// Validation-specific Word 1
// Validation-specific Word 2
// ...
//
// Each record consists of three subsections: members common across all
// validation, members specific to the stage, 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 Stage is the pipeline stage which has generated the error as defined
// by the SpvExecutionModel_ enumeration. This is used to interpret the
// following Stage-specific words.
//
// The Stage-specific Words identify which invocation of the shader generated
// the error. Every stage will write a fixed number of words. Vertex shaders
// will write the Vertex and Instance ID. Fragment shaders will write
// FragCoord.xy. Compute shaders will write the GlobalInvocation ID.
// The tessellation eval shader will write the Primitive ID and TessCoords.uv.
// The tessellation control shader and geometry shader will write the
// Primitive ID and Invocation ID.
//
// 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,
uint32_t stage_info_id,
const std::vector<uint32_t>& 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
@ -80,13 +178,37 @@ class InstDebugPrintfPass : public InstrumentPass {
void GenOutputCode(Instruction* printf_inst, uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* 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<Instruction> NewGlobalName(uint32_t id,
const std::string& name_str);
// Set the name for a structure member
std::unique_ptr<Instruction> 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_;
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

View File

@ -131,38 +131,6 @@ std::unique_ptr<Instruction> InstrumentPass::NewName(
{SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}});
}
std::unique_ptr<Instruction> InstrumentPass::NewGlobalName(
uint32_t id, const std::string& name_str) {
std::string prefixed_name;
switch (validation_id_) {
case kInstValidationIdBindless:
prefixed_name = "inst_bindless_";
break;
case kInstValidationIdBuffAddr:
prefixed_name = "inst_buff_addr_";
break;
case kInstValidationIdDebugPrintf:
prefixed_name = "inst_printf_";
break;
default:
assert(false); // add new instrumentation pass here
prefixed_name = "inst_pass_";
break;
}
prefixed_name += name_str;
return NewName(id, prefixed_name);
}
std::unique_ptr<Instruction> InstrumentPass::NewMemberName(
uint32_t id, uint32_t member_index, const std::string& name_str) {
return MakeUnique<Instruction>(
context(), spv::Op::OpMemberName, 0, 0,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_ID, {id}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index}},
{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
@ -195,24 +163,6 @@ uint32_t InstrumentPass::GenUintCastCode(uint32_t val_id,
->result_id();
}
void InstrumentPass::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 InstrumentPass::GenVarLoad(uint32_t var_id,
InstructionBuilder* builder) {
Instruction* var_inst = get_def_use_mgr()->GetDef(var_id);
@ -329,18 +279,6 @@ uint32_t InstrumentPass::GenStageInfo(uint32_t stage_idx,
return builder->AddCompositeConstruct(GetVec4UintId(), ids)->result_id();
}
void InstrumentPass::GenDebugStreamWrite(
uint32_t shader_id, uint32_t instruction_idx_id, uint32_t stage_info_id,
const std::vector<uint32_t>& 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<uint32_t>(validation_ids.size());
std::vector<uint32_t> args = {shader_id, instruction_idx_id, stage_info_id};
(void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
(void)builder->AddFunctionCall(GetVoidId(),
GetStreamWriteFunctionId(val_id_cnt), args);
}
bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) {
for (auto& id : ids) {
Instruction* id_inst = context()->get_def_use_mgr()->GetDef(id);
@ -349,14 +287,6 @@ bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) {
return true;
}
uint32_t InstrumentPass::GenDebugDirectRead(
const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
// Call debug input function. Pass func_idx and offset ids as args.
const uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
const uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
return GenReadFunctionCall(GetUintId(), input_func_id, offset_ids, builder);
}
uint32_t InstrumentPass::GenReadFunctionCall(
uint32_t return_id, uint32_t func_id,
const std::vector<uint32_t>& func_call_args,
@ -450,53 +380,6 @@ void InstrumentPass::UpdateSucceedingPhis(
});
}
uint32_t InstrumentPass::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 InstrumentPass::GetInputBufferTypeId() {
return (validation_id_ == kInstValidationIdBuffAddr) ? GetUint64Id()
: GetUintId();
}
uint32_t InstrumentPass::GetInputBufferPtrId() {
if (input_buffer_ptr_id_ == 0) {
input_buffer_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
GetInputBufferTypeId(), spv::StorageClass::StorageBuffer);
}
return input_buffer_ptr_id_;
}
uint32_t InstrumentPass::GetOutputBufferBinding() {
switch (validation_id_) {
case kInstValidationIdBindless:
return kDebugOutputBindingStream;
case kInstValidationIdBuffAddr:
return kDebugOutputBindingStream;
case kInstValidationIdDebugPrintf:
return kDebugOutputPrintfStream;
default:
assert(false && "unexpected validation id");
}
return 0;
}
uint32_t InstrumentPass::GetInputBufferBinding() {
switch (validation_id_) {
case kInstValidationIdBindless:
return kDebugInputBindingBindless;
case kInstValidationIdBuffAddr:
return kDebugInputBindingBuffAddr;
default:
assert(false && "unexpected validation id");
}
return 0;
}
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);
@ -577,110 +460,6 @@ void InstrumentPass::AddStorageBufferExt() {
storage_buffer_ext_defined_ = true;
}
// Return id for output buffer
uint32_t InstrumentPass::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<Instruction> 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 InstrumentPass::GetInputBufferId() {
if (input_buffer_id_ == 0) {
// If not created yet, create one
analysis::DecorationManager* deco_mgr = get_decoration_mgr();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
uint32_t width = (validation_id_ == kInstValidationIdBuffAddr) ? 64u : 32u;
analysis::Type* reg_uint_rarr_ty = GetUintRuntimeArrayType(width);
analysis::Struct* reg_buf_ty = GetStruct({reg_uint_rarr_ty});
uint32_t ibufTyId = 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(ibufTyId) == 0 &&
"used struct type returned");
deco_mgr->AddDecoration(ibufTyId, uint32_t(spv::Decoration::Block));
deco_mgr->AddMemberDecoration(ibufTyId, 0,
uint32_t(spv::Decoration::Offset), 0);
uint32_t ibufTyPtrId_ =
type_mgr->FindPointerToType(ibufTyId, spv::StorageClass::StorageBuffer);
input_buffer_id_ = TakeNextId();
std::unique_ptr<Instruction> newVarOp(new Instruction(
context(), spv::Op::OpVariable, ibufTyPtrId_, input_buffer_id_,
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{uint32_t(spv::StorageClass::StorageBuffer)}}}));
context()->AddGlobalValue(std::move(newVarOp));
context()->AddDebug2Inst(NewGlobalName(ibufTyId, "InputBuffer"));
context()->AddDebug2Inst(NewMemberName(ibufTyId, 0, "data"));
context()->AddDebug2Inst(NewGlobalName(input_buffer_id_, "input_buffer"));
deco_mgr->AddDecorationVal(
input_buffer_id_, uint32_t(spv::Decoration::DescriptorSet), desc_set_);
deco_mgr->AddDecorationVal(input_buffer_id_,
uint32_t(spv::Decoration::Binding),
GetInputBufferBinding());
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, {input_buffer_id_}});
context()->AnalyzeUses(&entry);
}
}
}
return input_buffer_id_;
}
uint32_t InstrumentPass::GetFloatId() {
if (float_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
@ -773,181 +552,6 @@ uint32_t InstrumentPass::GetVoidId() {
return void_id_;
}
uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t param_cnt) {
enum {
kShaderId = 0,
kInstructionIndex = 1,
kStageInfo = 2,
kFirstParam = 3,
};
// 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);
const analysis::Vector v4uint(uint_type, 4);
const analysis::Type* v4uint_type = type_mgr->GetRegisteredType(&v4uint);
std::vector<const analysis::Type*> param_types(kFirstParam + param_cnt,
uint_type);
param_types[kStageInfo] = v4uint_type;
std::unique_ptr<Function> output_func = StartFunction(
param2output_func_id_[param_cnt], type_mgr->GetVoidType(), param_types);
std::vector<uint32_t> param_ids = AddParameters(*output_func, param_types);
// Create first block
auto new_blk_ptr = MakeUnique<BasicBlock>(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 val_spec_offset = kInstStageOutCnt;
const uint32_t obuf_record_sz = val_spec_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<Instruction> merge_label(NewLabel(merge_blk_id));
std::unique_ptr<Instruction> 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<BasicBlock>(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);
// Store stage info. Stage Idx + 3 words of stage-specific data.
for (uint32_t i = 0; i < 4; ++i) {
Instruction* field =
builder.AddCompositeExtract(GetUintId(), param_ids[kStageInfo], {i});
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutStageIdx + i,
field->result_id(), &builder);
}
// Gen writes of validation specific data
for (uint32_t i = 0; i < param_cnt; ++i) {
GenDebugOutputFieldCode(obuf_curr_sz_id, val_spec_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<BasicBlock>(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 += std::to_string(param_cnt);
context()->AddDebug2Inst(
NewGlobalName(param2output_func_id_[param_cnt], name));
}
return param2output_func_id_[param_cnt];
}
uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
uint32_t func_id = param2input_func_id_[param_cnt];
if (func_id != 0) return func_id;
// Create input function for param_cnt.
func_id = TakeNextId();
analysis::Integer* uint_type = GetInteger(32, false);
std::vector<const analysis::Type*> param_types(param_cnt, uint_type);
std::unique_ptr<Function> input_func =
StartFunction(func_id, uint_type, param_types);
std::vector<uint32_t> param_ids = AddParameters(*input_func, param_types);
// Create block
auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
InstructionBuilder builder(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
// For each offset parameter, generate new offset with parameter, adding last
// loaded value if it exists, and load value from input buffer at new offset.
// Return last loaded value.
uint32_t ibuf_type_id = GetInputBufferTypeId();
uint32_t buf_id = GetInputBufferId();
uint32_t buf_ptr_id = GetInputBufferPtrId();
uint32_t last_value_id = 0;
for (uint32_t p = 0; p < param_cnt; ++p) {
uint32_t offset_id;
if (p == 0) {
offset_id = param_ids[0];
} else {
if (ibuf_type_id != GetUintId()) {
last_value_id =
builder.AddUnaryOp(GetUintId(), spv::Op::OpUConvert, last_value_id)
->result_id();
}
offset_id = builder.AddIAdd(GetUintId(), last_value_id, param_ids[p])
->result_id();
}
Instruction* ac_inst = builder.AddAccessChain(
buf_ptr_id, buf_id,
{builder.GetUintConstantId(kDebugInputDataOffset), offset_id});
last_value_id =
builder.AddLoad(ibuf_type_id, ac_inst->result_id())->result_id();
}
(void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, last_value_id);
// Close block and function and add function to module
input_func->AddBasicBlock(std::move(new_blk_ptr));
input_func->SetFunctionEnd(EndFunction());
context()->AddFunction(std::move(input_func));
std::string name("direct_read_");
name += std::to_string(param_cnt);
context()->AddDebug2Inst(NewGlobalName(func_id, name));
param2input_func_id_[param_cnt] = func_id;
return func_id;
}
void InstrumentPass::SplitBlock(
BasicBlock::iterator inst_itr, UptrVectorIterator<BasicBlock> block_itr,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
@ -1091,10 +695,6 @@ bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
}
void InstrumentPass::InitializeInstrument() {
output_buffer_id_ = 0;
output_buffer_ptr_id_ = 0;
input_buffer_ptr_id_ = 0;
input_buffer_id_ = 0;
float_id_ = 0;
v4float_id_ = 0;
uint_id_ = 0;

View File

@ -55,14 +55,6 @@
namespace spvtools {
namespace opt {
namespace {
// Validation Ids
// These are used to identify the general validation being done and map to
// its output buffers.
constexpr uint32_t kInstValidationIdBindless = 0;
constexpr uint32_t kInstValidationIdBuffAddr = 1;
constexpr uint32_t kInstValidationIdDebugPrintf = 2;
} // namespace
class InstrumentPass : public Pass {
using cbb_ptr = const BasicBlock*;
@ -85,12 +77,11 @@ class InstrumentPass : public Pass {
// 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, uint32_t validation_id,
InstrumentPass(uint32_t desc_set, uint32_t shader_id,
bool opt_direct_reads = false)
: Pass(),
desc_set_(desc_set),
shader_id_(shader_id),
validation_id_(validation_id),
opt_direct_reads_(opt_direct_reads) {}
// Initialize state for instrumentation of module.
@ -113,108 +104,9 @@ class InstrumentPass : public Pass {
void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
BasicBlock* new_blk_ptr);
// 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. It will also contain additional information to
// identify the instance of the shader, depending on the stage |stage_idx|
// of the shader. 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
// Stage
// Stage-specific Word 0
// Stage-specific Word 1
// ...
// Validation Error Code
// Validation-specific Word 0
// Validation-specific Word 1
// Validation-specific Word 2
// ...
//
// Each record consists of three subsections: members common across all
// validation, members specific to the stage, 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 Stage is the pipeline stage which has generated the error as defined
// by the SpvExecutionModel_ enumeration. This is used to interpret the
// following Stage-specific words.
//
// The Stage-specific Words identify which invocation of the shader generated
// the error. Every stage will write a fixed number of words. Vertex shaders
// will write the Vertex and Instance ID. Fragment shaders will write
// FragCoord.xy. Compute shaders will write the GlobalInvocation ID.
// The tessellation eval shader will write the Primitive ID and TessCoords.uv.
// The tessellation control shader and geometry shader will write the
// Primitive ID and Invocation ID.
//
// 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,
uint32_t stage_info_id,
const std::vector<uint32_t>& validation_ids,
InstructionBuilder* builder);
// Return true if all instructions in |ids| are constants or spec constants.
bool AllConstant(const std::vector<uint32_t>& ids);
// Generate in |builder| instructions to read the unsigned integer from the
// input buffer specified by the offsets in |offset_ids|. Given offsets
// o0, o1, ... oN, and input buffer ibuf, return the id for the value:
//
// ibuf[...ibuf[ibuf[o0]+o1]...+oN]
//
// The binding and the format of the input buffer is determined by each
// specific validation, which is specified at the creation of the pass.
uint32_t GenDebugDirectRead(const std::vector<uint32_t>& offset_ids,
InstructionBuilder* builder);
uint32_t GenReadFunctionCall(uint32_t return_id, uint32_t func_id,
const std::vector<uint32_t>& args,
InstructionBuilder* builder);
@ -243,15 +135,6 @@ class InstrumentPass : public Pass {
std::unique_ptr<Instruction> NewName(uint32_t id,
const std::string& name_str);
// Set the name for a function or global variable, names will be
// prefixed to identify which instrumentation pass generated them.
std::unique_ptr<Instruction> NewGlobalName(uint32_t id,
const std::string& name_str);
// Set the name for a structure member
std::unique_ptr<Instruction> NewMemberName(uint32_t id, uint32_t member_index,
const std::string& name_str);
// Return id for 32-bit unsigned type
uint32_t GetUintId();
@ -283,30 +166,9 @@ class InstrumentPass : public Pass {
// Return pointer to type for runtime array of uint
analysis::RuntimeArray* GetUintRuntimeArrayType(uint32_t width);
// Return id for buffer uint type
uint32_t GetOutputBufferPtrId();
// Return id for buffer uint type
uint32_t GetInputBufferTypeId();
// Return id for buffer uint type
uint32_t GetInputBufferPtrId();
// Return binding for output buffer for current validation.
uint32_t GetOutputBufferBinding();
// Return binding for input buffer for current validation.
uint32_t GetInputBufferBinding();
// Add storage buffer extension if needed
void AddStorageBufferExt();
// Return id for debug output buffer
uint32_t GetOutputBufferId();
// Return id for debug input buffer
uint32_t GetInputBufferId();
// Return id for 32-bit float type
uint32_t GetFloatId();
@ -322,14 +184,6 @@ class InstrumentPass : public Pass {
// Return id for v3uint type
uint32_t GetVec3UintId();
// 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);
// Return id for input function taking |param_cnt| uint32 parameters. Define
// if it doesn't exist.
uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
// 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,
@ -349,12 +203,6 @@ class InstrumentPass : public Pass {
std::queue<uint32_t>* roots,
uint32_t stage_idx);
// 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 into |builder| which will load |var_id| and return
// its result id.
uint32_t GenVarLoad(uint32_t var_id, InstructionBuilder* builder);
@ -395,62 +243,47 @@ class InstrumentPass : public Pass {
// Map from instruction's unique id to offset in original file.
std::unordered_map<uint32_t, uint32_t> uid2offset_;
// result id for OpConstantFalse
uint32_t validation_id_;
// id for output buffer variable
uint32_t output_buffer_id_;
// ptr type id for output buffer element
uint32_t output_buffer_ptr_id_;
// ptr type id for input buffer element
uint32_t input_buffer_ptr_id_;
// id for debug output function
std::unordered_map<uint32_t, uint32_t> param2output_func_id_;
// ids for debug input functions
std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
// id for input buffer variable
uint32_t input_buffer_id_;
// id for 32-bit float type
uint32_t float_id_;
uint32_t float_id_{0};
// id for v4float type
uint32_t v4float_id_;
uint32_t v4float_id_{0};
// id for v4uint type
uint32_t v4uint_id_;
uint32_t v4uint_id_{0};
// id for v3uint type
uint32_t v3uint_id_;
uint32_t v3uint_id_{0};
// id for 32-bit unsigned type
uint32_t uint_id_;
uint32_t uint_id_{0};
// id for 64-bit unsigned type
uint32_t uint64_id_;
uint32_t uint64_id_{0};
// id for 8-bit unsigned type
uint32_t uint8_id_;
uint32_t uint8_id_{0};
// id for bool type
uint32_t bool_id_;
uint32_t bool_id_{0};
// id for void type
uint32_t void_id_;
uint32_t void_id_{0};
// boolean to remember storage buffer extension
bool storage_buffer_ext_defined_;
bool storage_buffer_ext_defined_{false};
// runtime array of uint type
analysis::RuntimeArray* uint64_rarr_ty_;
analysis::RuntimeArray* uint64_rarr_ty_{nullptr};
// runtime array of uint type
analysis::RuntimeArray* uint32_rarr_ty_;
analysis::RuntimeArray* uint32_rarr_ty_{nullptr};
// Pre-instrumentation same-block insts
std::unordered_map<uint32_t, Instruction*> same_block_pre_;
@ -475,11 +308,11 @@ class InstrumentPass : public Pass {
std::unordered_map<std::vector<uint32_t>, uint32_t, vector_hash_> call2id_;
// Function currently being instrumented
Function* curr_func_;
Function* curr_func_{nullptr};
// Optimize direct debug input buffer reads. Specifically, move all such
// reads with constant args to first block and reuse them.
bool opt_direct_reads_;
bool opt_direct_reads_{false};
};
} // namespace opt

View File

@ -252,6 +252,8 @@ class IRContext {
inline void AddType(std::unique_ptr<Instruction>&& t);
// Appends a constant, global variable, or OpUndef instruction to this module.
inline void AddGlobalValue(std::unique_ptr<Instruction>&& v);
// Prepends a function declaration to this module.
inline void AddFunctionDeclaration(std::unique_ptr<Function>&& f);
// Appends a function to this module.
inline void AddFunction(std::unique_ptr<Function>&& f);
@ -1213,6 +1215,10 @@ void IRContext::AddGlobalValue(std::unique_ptr<Instruction>&& v) {
module()->AddGlobalValue(std::move(v));
}
void IRContext::AddFunctionDeclaration(std::unique_ptr<Function>&& f) {
module()->AddFunctionDeclaration(std::move(f));
}
void IRContext::AddFunction(std::unique_ptr<Function>&& f) {
module()->AddFunction(std::move(f));
}

View File

@ -120,6 +120,9 @@ class Module {
// Appends a constant, global variable, or OpUndef instruction to this module.
inline void AddGlobalValue(std::unique_ptr<Instruction> v);
// Prepends a function declaration to this module.
inline void AddFunctionDeclaration(std::unique_ptr<Function> f);
// Appends a function to this module.
inline void AddFunction(std::unique_ptr<Function> f);
@ -380,6 +383,11 @@ inline void Module::AddGlobalValue(std::unique_ptr<Instruction> v) {
types_values_.push_back(std::move(v));
}
inline void Module::AddFunctionDeclaration(std::unique_ptr<Function> f) {
// function declarations must come before function definitions.
functions_.emplace(functions_.begin(), std::move(f));
}
inline void Module::AddFunction(std::unique_ptr<Function> f) {
functions_.emplace_back(std::move(f));
}

View File

@ -434,13 +434,13 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
pass_name == "inst-desc-idx-check" ||
pass_name == "inst-buff-oob-check") {
// preserve legacy names
RegisterPass(CreateInstBindlessCheckPass(7, 23));
RegisterPass(CreateInstBindlessCheckPass(23));
RegisterPass(CreateSimplificationPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateBlockMergePass());
RegisterPass(CreateAggressiveDCEPass(true));
} else if (pass_name == "inst-buff-addr-check") {
RegisterPass(CreateInstBuffAddrCheckPass(7, 23));
RegisterPass(CreateInstBuffAddrCheckPass(23));
RegisterPass(CreateAggressiveDCEPass(true));
} else if (pass_name == "convert-relaxed-to-half") {
RegisterPass(CreateConvertRelaxedToHalfPass());
@ -980,10 +980,9 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
MakeUnique<opt::UpgradeMemoryModel>());
}
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
uint32_t shader_id) {
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t shader_id) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
MakeUnique<opt::InstBindlessCheckPass>(shader_id));
}
Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
@ -992,10 +991,9 @@ Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
MakeUnique<opt::InstDebugPrintfPass>(desc_set, shader_id));
}
Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t desc_set,
uint32_t shader_id) {
Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t shader_id) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InstBuffAddrCheckPass>(desc_set, shader_id));
MakeUnique<opt::InstBuffAddrCheckPass>(shader_id));
}
Optimizer::PassToken CreateConvertRelaxedToHalfPass() {

File diff suppressed because it is too large Load Diff

View File

@ -26,123 +26,14 @@ namespace spvtools {
namespace opt {
namespace {
static const std::string kOutputDecorations = R"(
; CHECK: OpDecorate [[output_buffer_type:%inst_buff_addr_OutputBuffer]] Block
; CHECK: OpMemberDecorate [[output_buffer_type]] 0 Offset 0
; CHECK: OpMemberDecorate [[output_buffer_type]] 1 Offset 4
; CHECK: OpDecorate [[output_buffer_var:%\w+]] DescriptorSet 7
; CHECK: OpDecorate [[output_buffer_var]] Binding 0
static const std::string kFuncName = "inst_buff_addr_search_and_test";
static const std::string kImportDeco = R"(
;CHECK: OpDecorate %)" + kFuncName + R"( LinkageAttributes ")" +
kFuncName + R"(" Import
)";
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
)";
static const std::string kStreamWrite3 = R"(
; CHECK: %inst_buff_addr_stream_write_3 = OpFunction %void None {{%\w+}}
; CHECK: [[sw_shader_id:%\w+]] = OpFunctionParameter %uint
; CHECK: [[sw_inst_idx:%\w+]] = OpFunctionParameter %uint
; CHECK: [[sw_stage_info:%\w+]] = OpFunctionParameter %v4uint
; CHECK: [[sw_param_1:%\w+]] = OpFunctionParameter %uint
; CHECK: [[sw_param_2:%\w+]] = OpFunctionParameter %uint
; CHECK: [[sw_param_3:%\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_10
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_10
; 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_10
; 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+}} = OpCompositeExtract %uint [[sw_stage_info]] 0
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
; CHECK: OpStore {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpCompositeExtract %uint [[sw_stage_info]] 1
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
; CHECK: OpStore {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpCompositeExtract %uint [[sw_stage_info]] 2
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
; CHECK: OpStore {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpCompositeExtract %uint [[sw_stage_info]] 3
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_6
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
; CHECK: OpStore {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_7
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
; CHECK: OpStore {{%\w+}} [[sw_param_1]]
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_8
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
; CHECK: OpStore {{%\w+}} [[sw_param_2]]
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_9
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
; CHECK: OpStore {{%\w+}} [[sw_param_3]]
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: OpReturn
; CHECK: OpFunctionEnd
)";
static const std::string kInputDecorations = R"(
; CHECK: OpDecorate [[input_buffer_type:%inst_buff_addr_InputBuffer]] Block
; CHECK: OpMemberDecorate [[input_buffer_type]] 0 Offset 0
; CHECK: OpDecorate [[input_buffer_var:%\w+]] DescriptorSet 7
; CHECK: OpDecorate [[input_buffer_var]] Binding 2
)";
static const std::string kInputGlobals = R"(
; CHECK: [[input_buffer_type]] = OpTypeStruct %_runtimearr_ulong
; CHECK: [[input_ptr_type:%\w+]] = OpTypePointer StorageBuffer [[input_buffer_type]]
; CHECK: [[input_buffer_var]] = OpVariable [[input_ptr_type]] StorageBuffer
)";
static const std::string kSearchAndTest = R"(
; CHECK: {{%\w+}} = OpFunction %bool None {{%\w+}}
; CHECK: [[param_1:%\w+]] = OpFunctionParameter %ulong
; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
; CHECK: {{%\w+}} = OpLabel
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpPhi %uint %uint_1 {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: OpLoopMerge {{%\w+}} {{%\w+}} None
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_1
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_ulong [[input_buffer_var]] %uint_0 {{%\w+}}
; CHECK: {{%\w+}} = OpLoad %ulong {{%\w+}}
; CHECK: {{%\w+}} = OpUGreaterThan %bool {{%\w+}} [[param_1]]
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpISub %uint {{%\w+}} %uint_1
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_ulong [[input_buffer_var]] %uint_0 {{%\w+}}
; CHECK: {{%\w+}} = OpLoad %ulong {{%\w+}}
; CHECK: {{%\w+}} = OpISub %ulong [[param_1]] {{%\w+}}
; CHECK: {{%\w+}} = OpUConvert %ulong [[param_2]]
; CHECK: {{%\w+}} = OpIAdd %ulong {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_ulong [[input_buffer_var]] %uint_0 %uint_0
; CHECK: {{%\w+}} = OpLoad %ulong {{%\w+}}
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpISub %uint {{%\w+}} %uint_1
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_ulong [[input_buffer_var]] %uint_0 {{%\w+}}
; CHECK: {{%\w+}} = OpLoad %ulong {{%\w+}}
; CHECK: {{%\w+}} = OpULessThanEqual %bool {{%\w+}} {{%\w+}}
; CHECK: OpReturnValue {{%\w+}}
; CHECK: OpFunctionEnd
static const std::string kImportStub = R"(
;CHECK: %)" + kFuncName + R"( = OpFunction %bool None {{%\w+}}
;CHECK: OpFunctionEnd
)";
// clang-format on
@ -171,13 +62,13 @@ TEST_F(InstBuffAddrTest, InstPhysicalStorageBufferStore) {
const std::string defs = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
; CHECK: OpCapability Int64
;CHECK: OpCapability Int64
OpExtension "SPV_EXT_physical_storage_buffer"
; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
;CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint GLCompute %main "main"
; CHECK: OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
;CHECK: OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
OpExecutionMode %main LocalSize 1 1 1
OpSource GLSL 450
OpSourceExtension "GL_EXT_buffer_reference"
@ -202,11 +93,8 @@ OpMemberDecorate %bufStruct 1 Offset 32
OpDecorate %bufStruct Block
OpDecorate %u_info DescriptorSet 0
OpDecorate %u_info Binding 0
; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8
)" + kInputDecorations + R"(
; CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
)" + kOutputDecorations + R"(
)" + kImportDeco + R"(
;CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
)";
const std::string globals = R"(
@ -227,17 +115,11 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_bufStruct PhysicalStorageBuffer
%int_1 = OpConstant %int 1
%int_3239 = OpConstant %int 3239
%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
; CHECK: %ulong = OpTypeInt 64 0
; CHECK: %bool = OpTypeBool
; CHECK: %_runtimearr_ulong = OpTypeRuntimeArray %ulong
)" + kInputGlobals + R"(
; CHECK: %_ptr_StorageBuffer_ulong = OpTypePointer StorageBuffer %ulong
; CHECK: %v3uint = OpTypeVector %uint 3
; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
; CHECK: %gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
)" + kOutputGlobals + R"(
; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
;CHECK: %ulong = OpTypeInt 64 0
;CHECK: %bool = OpTypeBool
;CHECK: %v3uint = OpTypeVector %uint 3
;CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
;CHECK: %gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
)";
// clang-format off
@ -247,41 +129,35 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_bufStruct PhysicalStorageBuffer
%17 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
%18 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %17
%22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %18 %int_1
; CHECK-NOT: %17 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
; CHECK-NOT: %18 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %17
; CHECK-NOT: %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %18 %int_1
; CHECK: %20 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
; CHECK: %21 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %20
; CHECK: %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %21 %int_1
; CHECK: {{%\w+}} = OpConvertPtrToU %ulong %22
; CHECK: {{%\w+}} = OpFunctionCall %bool %inst_buff_addr_search_and_test {{%\w+}} %uint_4
; CHECK: OpSelectionMerge {{%\w+}} None
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
;CHECK-NOT: %17 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
;CHECK-NOT: %18 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %17
;CHECK-NOT: %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %18 %int_1
;CHECK: %20 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
;CHECK: %21 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %20
;CHECK: %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %21 %int_1
;CHECK: {{%\w+}} = OpConvertPtrToU %ulong %22
;CHECK: {{%\w+}} = OpLoad %v3uint %gl_GlobalInvocationID
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 2
;CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_5 {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpFunctionCall %bool %)" + kFuncName + R"( %uint_23 %uint_49 {{%\w+}} {{%\w+}} %uint_4
;CHECK: OpSelectionMerge {{%\w+}} None
;CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
OpStore %22 %int_3239 Aligned 16
; CHECK: OpStore %22 %int_3239 Aligned 16
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpShiftRightLogical %ulong {{%\w+}} %uint_32
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpLoad %v3uint %gl_GlobalInvocationID
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 2
; CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_5 {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpFunctionCall %void %inst_buff_addr_stream_write_3 %uint_23 %uint_48 {{%\w+}} %uint_3 {{%\w+}} {{%\w+}}
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
;CHECK: OpStore %22 %int_3239 Aligned 16
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string output_funcs = kSearchAndTest + kStreamWrite3;
// SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(
defs + decorates + globals + main_func + output_funcs, true, 7u, 23u);
defs + decorates + globals + kImportStub + main_func, true, 23u);
}
TEST_F(InstBuffAddrTest, InstPhysicalStorageBufferLoadAndStore) {
@ -311,7 +187,7 @@ TEST_F(InstBuffAddrTest, InstPhysicalStorageBufferLoadAndStore) {
const std::string defs = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
; CHECK: OpCapability Int64
;CHECK: OpCapability Int64
OpExtension "SPV_EXT_physical_storage_buffer"
OpExtension "SPV_KHR_storage_buffer_storage_class"
%1 = OpExtInstImport "GLSL.std.450"
@ -321,7 +197,7 @@ OpExecutionMode %main LocalSize 1 1 1
OpSource GLSL 450
OpSourceExtension "GL_EXT_buffer_reference"
OpName %main "main"
; CHECK: OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
;CHECK: OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
OpName %blockType "blockType"
OpMemberName %blockType 0 "x"
OpMemberName %blockType 1 "next"
@ -339,12 +215,9 @@ OpMemberDecorate %rootBlock 0 Offset 0
OpDecorate %rootBlock Block
OpDecorate %r DescriptorSet 0
OpDecorate %r Binding 0
; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8
)" + kInputDecorations + R"(
; CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
)" + kOutputDecorations;
// clang-format on
)" + kImportDeco + R"(
;CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
)";
const std::string globals = R"(
%void = OpTypeVoid
@ -362,7 +235,7 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_blockType PhysicalStorageBuffer
%_ptr_PhysicalStorageBuffer__ptr_PhysicalStorageBuffer_blockType = OpTypePointer PhysicalStorageBuffer %_ptr_PhysicalStorageBuffer_blockType
%int_531 = OpConstant %int 531
%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
)" + kInputGlobals + kOutputGlobals;
)";
const std::string main_func = R"(
%main = OpFunction %void None %3
@ -373,58 +246,49 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_blockType PhysicalStorageBuffer
%22 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
%26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %22 %int_0
OpStore %26 %int_531 Aligned 16
; CHECK-NOT: %22 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
; CHECK-NOT: %26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %22 %int_0
; CHECK: %30 = OpConvertPtrToU %ulong %21
; CHECK: %67 = OpFunctionCall %bool %inst_buff_addr_search_and_test %30 %uint_8
; CHECK: OpSelectionMerge %68 None
; CHECK: OpBranchConditional %67 %69 %70
; CHECK: %69 = OpLabel
; CHECK: %71 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
; CHECK: OpBranch %68
; CHECK: %70 = OpLabel
; CHECK: %72 = OpUConvert %uint %30
; CHECK: %74 = OpShiftRightLogical %ulong %30 %uint_32
; CHECK: %75 = OpUConvert %uint %74
; CHECK: {{%\w+}} = OpLoad %v3uint %gl_GlobalInvocationID
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 2
; CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_5 {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpFunctionCall %void %inst_buff_addr_stream_write_3 %uint_23 %uint_44 {{%\w+}} %uint_3 %72 %75
; CHECK: {{%\w+}} = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_blockType {{%\w+}}
; CHECK: OpBranch %68
; CHECK: %68 = OpLabel
; CHECK: {{%\w+}} = OpPhi %_ptr_PhysicalStorageBuffer_blockType %71 %69 {{%\w+}} %70
; CHECK: %26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int {{%\w+}} %int_0
; CHECK: {{%\w+}} = OpConvertPtrToU %ulong %26
; CHECK: {{%\w+}} = OpFunctionCall %bool %inst_buff_addr_search_and_test {{%\w+}} %uint_4
; CHECK: OpSelectionMerge {{%\w+}} None
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: OpStore %26 %int_531 Aligned 16
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpShiftRightLogical %ulong {{%\w+}} %uint_32
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpLoad %v3uint %gl_GlobalInvocationID
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 2
; CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_5 {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpFunctionCall %void %inst_buff_addr_stream_write_3 %uint_23 %uint_46 {{%\w+}} %uint_3 {{%\w+}} {{%\w+}}
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
;CHECK-NOT: %22 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
;CHECK-NOT: %26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %22 %int_0
;CHECK: {{%\w+}} = OpConvertPtrToU %ulong %21
;CHECK: {{%\w+}} = OpLoad %v3uint %gl_GlobalInvocationID
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 2
;CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_5 {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpFunctionCall %bool %)" + kFuncName + R"( %uint_23 %uint_45 {{%\w+}} {{%\w+}} %uint_8
;CHECK: OpSelectionMerge {{%\w+}} None
;CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: {{%\w+}} = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: {{%\w+}} = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_blockType %52
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: {{%\w+}} = OpPhi %_ptr_PhysicalStorageBuffer_blockType {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: %26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int {{%\w+}} %int_0
;CHECK: {{%\w+}} = OpConvertPtrToU %ulong %26
;CHECK: {{%\w+}} = OpLoad %v3uint %gl_GlobalInvocationID
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 2
;CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_5 {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpFunctionCall %bool %)" + kFuncName + R"( %uint_23 %uint_47 {{%\w+}} {{%\w+}} %uint_4
;CHECK: OpSelectionMerge {{%\w+}} None
;CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: OpStore %26 %int_531 Aligned 16
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string output_funcs = kSearchAndTest + kStreamWrite3;
// clang-format on
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(
defs + decorates + globals + main_func + output_funcs, true, 7u, 23u);
defs + decorates + globals + kImportStub + main_func, true, 23u);
}
TEST_F(InstBuffAddrTest, StructLoad) {
@ -451,11 +315,11 @@ TEST_F(InstBuffAddrTest, StructLoad) {
OpCapability Shader
OpCapability Int64
OpCapability PhysicalStorageBufferAddresses
; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
;CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
; CHECK: OpEntryPoint Fragment %main "main" %inst_buff_addr_input_buffer %gl_FragCoord %inst_buff_addr_output_buffer
;CHECK: OpEntryPoint Fragment %main "main" %gl_FragCoord
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpSourceExtension "GL_ARB_gpu_shader_int64"
@ -474,11 +338,9 @@ OpMemberName %TestBuffer 0 "test"
OpMemberDecorate %Test_0 0 Offset 0
OpMemberDecorate %TestBuffer 0 Offset 0
OpDecorate %TestBuffer Block
; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8
)" + kInputDecorations + R"(
; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
)" + kOutputDecorations;
)" + kImportDeco + R"(
;CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
)";
const std::string globals = R"(
%void = OpTypeVoid
@ -494,53 +356,44 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_TestBuffer PhysicalStorageBuffe
%int_0 = OpConstant %int 0
%_ptr_PhysicalStorageBuffer_Test_0 = OpTypePointer PhysicalStorageBuffer %Test_0
%ulong_18446744073172680704 = OpConstant %ulong 18446744073172680704
; CHECK: %47 = OpTypeFunction %bool %ulong %uint
)" + kInputGlobals + kOutputGlobals + R"(
; CHECK: {{%\w+}} = OpConstantNull %Test_0
;CHECK: {{%\w+}} = OpConstantNull %Test_0
)";
// clang-format on
const std::string main_func =
R"(
const std::string main_func = R"(
%main = OpFunction %void None %3
%5 = OpLabel
%37 = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_TestBuffer %ulong_18446744073172680704
%38 = OpAccessChain %_ptr_PhysicalStorageBuffer_Test_0 %37 %int_0
%39 = OpLoad %Test_0 %38 Aligned 16
; CHECK-NOT: %39 = OpLoad %Test_0 %38 Aligned 16
; CHECK: {{%\w+}} = OpConvertPtrToU %ulong %38
; CHECK: {{%\w+}} = OpFunctionCall %bool %inst_buff_addr_search_and_test {{%\w+}} %uint_4
; CHECK: OpSelectionMerge {{%\w+}} None
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpLoad %Test_0 %38 Aligned 16
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpShiftRightLogical %ulong {{%\w+}} %uint_32
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpLoad %v4float %gl_FragCoord
; CHECK: {{%\w+}} = OpBitcast %v4uint {{%\w+}}
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
; CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_4 {{%\w+}} {{%\w+}} %uint_0
; CHECK: {{%\w+}} = OpFunctionCall %void %inst_buff_addr_stream_write_3 %uint_23 %uint_37 {{%\w+}} %uint_3 {{%\w+}} {{%\w+}}
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: [[phi_result:%\w+]] = OpPhi %Test_0 {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK-NOT: %39 = OpLoad %Test_0 %38 Aligned 16
;CHECK: {{%\w+}} = OpConvertPtrToU %ulong %38
;CHECK: {{%\w+}} = OpLoad %v4float %gl_FragCoord
;CHECK: {{%\w+}} = OpBitcast %v4uint {{%\w+}}
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
;CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
;CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_4 {{%\w+}} {{%\w+}} %uint_0
;CHECK: {{%\w+}} = OpFunctionCall %bool %)" + kFuncName + R"( %uint_23 %uint_38 {{%\w+}} {{%\w+}} %uint_4
;CHECK: OpSelectionMerge {{%\w+}} None
;CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: {{%\w+}} = OpLoad %Test_0 %38 Aligned 16
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: [[phi_result:%\w+]] = OpPhi %Test_0 {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}}
%40 = OpCopyLogical %Test %39
; CHECK-NOT: %40 = OpCopyLogical %Test %39
; CHECK: %40 = OpCopyLogical %Test [[phi_result]]
;CHECK-NOT: %40 = OpCopyLogical %Test %39
;CHECK: %40 = OpCopyLogical %Test [[phi_result]]
OpReturn
OpFunctionEnd
)";
// clang-format on
const std::string output_funcs = kSearchAndTest + kStreamWrite3;
SetTargetEnv(SPV_ENV_VULKAN_1_2);
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(
defs + decorates + globals + main_func + output_funcs, true);
defs + decorates + globals + kImportStub + main_func, true);
}
TEST_F(InstBuffAddrTest, PaddedStructLoad) {
@ -599,12 +452,9 @@ OpMemberDecorate %Test_0 0 Offset 0
OpMemberDecorate %Test_0 1 Offset 16
OpMemberDecorate %Test_0 2 Offset 24
OpMemberDecorate %TestBuffer 0 Offset 0
; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8
)" + kInputDecorations + R"(
; CHECK: OpDecorate %gl_VertexIndex BuiltIn VertexIndex
; CHECK: OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
)" + kOutputDecorations + R"(
)" + kImportDeco + R"(
;CHECK: OpDecorate %gl_VertexIndex BuiltIn VertexIndex
;CHECK: OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
)";
const std::string globals = R"(
@ -627,13 +477,10 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_TestBuffer PhysicalStorageBuffe
%_ptr_PhysicalStorageBuffer_Test_0 = OpTypePointer PhysicalStorageBuffer %Test_0
%_ptr_Function_Test = OpTypePointer Function %Test
%ulong_18446744073172680704 = OpConstant %ulong 18446744073172680704
)" + kInputGlobals + kOutputGlobals + R"(
; CHECK: {{%\w+}} = OpConstantNull %Test_0
;CHECK: {{%\w+}} = OpConstantNull %Test_0
)";
// clang-format on
const std::string main_func =
R"(
const std::string main_func = R"(
%main = OpFunction %void None %3
%5 = OpLabel
%param = OpVariable %_ptr_Function_ulong Function
@ -650,40 +497,35 @@ OpFunctionEnd
%25 = OpAccessChain %_ptr_PhysicalStorageBuffer_Test_0 %21 %int_0
%26 = OpLoad %Test_0 %25 Aligned 16
%29 = OpCopyLogical %Test %26
; CHECK-NOT: %30 = OpLoad %Test %28
; CHECK-NOT: %26 = OpLoad %Test_0 %25 Aligned 16
; CHECK-NOT: %29 = OpCopyLogical %Test %26
; CHECK: {{%\w+}} = OpConvertPtrToU %ulong %25
; CHECK: {{%\w+}} = OpFunctionCall %bool %inst_buff_addr_search_and_test {{%\w+}} %uint_28
; CHECK: OpSelectionMerge {{%\w+}} None
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpLoad %Test_0 %25 Aligned 16
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpShiftRightLogical %ulong {{%\w+}} %uint_32
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpLoad %uint %gl_VertexIndex
; CHECK: {{%\w+}} = OpLoad %uint %gl_InstanceIndex
; CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_0 {{%\w+}} {{%\w+}} %uint_0
; CHECK: {{%\w+}} = OpFunctionCall %void %inst_buff_addr_stream_write_3 %uint_23 %uint_62 {{%\w+}} %uint_3 {{%\w+}} {{%\w+}}
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: [[phi_result:%\w+]] = OpPhi %Test_0 {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: %29 = OpCopyLogical %Test [[phi_result]]
;CHECK-NOT: %30 = OpLoad %Test %28
;CHECK-NOT: %26 = OpLoad %Test_0 %25 Aligned 16
;CHECK-NOT: %29 = OpCopyLogical %Test %26
;CHECK: {{%\w+}} = OpConvertPtrToU %ulong %25
;CHECK: {{%\w+}} = OpLoad %uint %gl_VertexIndex
;CHECK: {{%\w+}} = OpLoad %uint %gl_InstanceIndex
;CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_0 {{%\w+}} {{%\w+}} %uint_0
;CHECK: {{%\w+}} = OpFunctionCall %bool %)" + kFuncName + R"( %uint_23 %uint_63 {{%\w+}} {{%\w+}} %uint_28
;CHECK: OpSelectionMerge {{%\w+}} None
;CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: {{%\w+}} = OpLoad %Test_0 %25 Aligned 16
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: [[phi_result:%\w+]] = OpPhi %Test_0 {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: %29 = OpCopyLogical %Test [[phi_result]]
OpStore %28 %29
%30 = OpLoad %Test %28
OpReturnValue %30
OpFunctionEnd
)";
// clang-format on
const std::string output_funcs = kSearchAndTest + kStreamWrite3;
SetTargetEnv(SPV_ENV_VULKAN_1_2);
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(
defs + decorates + globals + main_func + output_funcs, true);
defs + decorates + globals + kImportStub + main_func, true);
}
TEST_F(InstBuffAddrTest, DeviceBufferAddressOOB) {
@ -710,7 +552,7 @@ OpCapability PhysicalStorageBufferAddresses
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Vertex %main "main" %u_info
;CHECK: OpEntryPoint Vertex %main "main" %u_info %inst_buff_addr_input_buffer %gl_VertexIndex %gl_InstanceIndex %inst_buff_addr_output_buffer
;CHECK: OpEntryPoint Vertex %main "main" %u_info %gl_VertexIndex %gl_InstanceIndex
OpSource GLSL 450
OpSourceExtension "GL_EXT_buffer_reference"
OpName %main "main"
@ -729,7 +571,7 @@ OpMemberDecorate %bufStruct 0 Offset 0
OpDecorate %bufStruct Block
OpDecorate %u_info DescriptorSet 0
OpDecorate %u_info Binding 0
)" + kInputDecorations + kOutputDecorations + R"(
)" + kImportDeco + R"(
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
@ -750,7 +592,7 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_bufStruct PhysicalStorageBuffer
%_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct = OpTypePointer Uniform %_ptr_PhysicalStorageBuffer_bufStruct
%int_n559035791 = OpConstant %int -559035791
%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
)" + kInputGlobals + kOutputGlobals + R"(
)" + kImportStub + R"(
%main = OpFunction %void None %3
%5 = OpLabel
%i = OpVariable %_ptr_Function_int Function
@ -770,21 +612,18 @@ OpBranchConditional %29 %11 %12
%32 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %31
%33 = OpLoad %int %i
%36 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %32 %int_0 %33
;CHECK: %41 = OpConvertPtrToU %ulong %36
;CHECK: %76 = OpFunctionCall %bool %inst_buff_addr_search_and_test %41 %uint_4
;CHECK: OpSelectionMerge %77 None
;CHECK: OpBranchConditional %76 %78 %79
;CHECK: %78 = OpLabel
OpStore %36 %int_n559035791 Aligned 16
;CHECK: OpBranch %77
;CHECK: %79 = OpLabel
;CHECK: %80 = OpUConvert %uint %41
;CHECK: %82 = OpShiftRightLogical %ulong %41 %uint_32
;CHECK: %83 = OpUConvert %uint %82
;CHECK: {{%\w+}} = OpConvertPtrToU %ulong %36
;CHECK: {{%\w+}} = OpLoad %uint %gl_VertexIndex
;CHECK: {{%\w+}} = OpLoad %uint %gl_InstanceIndex
;CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_0 {{%\w+}} {{%\w+}} %uint_0
;CHECK: {{%\w+}} = OpFunctionCall %void %inst_buff_addr_stream_write_3 %uint_23 %uint_62 {{%\w+}} %uint_3 {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpFunctionCall %bool %)" + kFuncName + R"( %uint_23 %uint_63 {{%\w+}} {{%\w+}} %uint_4
;CHECK: OpSelectionMerge {{%\w+}} None
;CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: OpStore %36 %int_n559035791 Aligned 16
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
OpBranch %13
@ -795,12 +634,12 @@ OpStore %i %38
OpBranch %10
%12 = OpLabel
OpReturn
OpFunctionEnd)" + kSearchAndTest + kStreamWrite3;
OpFunctionEnd)";
// clang-format on
SetTargetEnv(SPV_ENV_VULKAN_1_2);
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(text, true, 7, 23);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(text, true, 23);
}
TEST_F(InstBuffAddrTest, UVec3ScalarAddressOOB) {
@ -849,7 +688,7 @@ OpMemberDecorate %IndexBuffer 0 Offset 0
OpDecorate %IndexBuffer Block
OpDecorate %u_info DescriptorSet 0
OpDecorate %u_info Binding 0
)" + kInputDecorations + kOutputDecorations + R"(
)" + kImportDeco + R"(
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
@ -867,7 +706,7 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_IndexBuffer PhysicalStorageBuff
%int_1 = OpConstant %int 1
%_ptr_Uniform_int = OpTypePointer Uniform %int
%bool = OpTypeBool
)" + kInputGlobals + kOutputGlobals + R"(
)" + kImportStub + R"(
%_ptr_Function_v3uint = OpTypePointer Function %v3uint
%_ptr_Uniform__ptr_PhysicalStorageBuffer_IndexBuffer = OpTypePointer Uniform %_ptr_PhysicalStorageBuffer_IndexBuffer
%_ptr_PhysicalStorageBuffer_v3uint = OpTypePointer PhysicalStorageBuffer %v3uint
@ -893,27 +732,23 @@ OpBranchConditional %29 %11 %12
%37 = OpAccessChain %_ptr_PhysicalStorageBuffer_v3uint %34 %int_0 %35
%38 = OpLoad %v3uint %37 Aligned 4
OpStore %readvec %38
; CHECK-NOT: %38 = OpLoad %v3uint %37 Aligned 4
; CHECK-NOT: OpStore %readvec %38
; CHECK: {{%\w+}} = OpConvertPtrToU %ulong %37
; CHECK: [[test_result:%\w+]] = OpFunctionCall %bool %inst_buff_addr_search_and_test {{%\w+}} %uint_12
; CHECK: OpSelectionMerge {{%\w+}} None
; CHECK: OpBranchConditional [[test_result]] {{%\w+}} {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpLoad %v3uint %37 Aligned 4
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpShiftRightLogical %ulong {{%\w+}} %uint_32
; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
; CHECK: {{%\w+}} = OpLoad %uint %gl_VertexIndex
; CHECK: {{%\w+}} = OpLoad %uint %gl_InstanceIndex
; CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_0 {{%\w+}} {{%\w+}} %uint_0
; CHECK: {{%\w+}} = OpFunctionCall %void %inst_buff_addr_stream_write_3 %uint_23 %uint_66 {{%\w+}} %uint_3 {{%\w+}} {{%\w+}}
; CHECK: OpBranch {{%\w+}}
; CHECK: {{%\w+}} = OpLabel
; CHECK: [[phi_result:%\w+]] = OpPhi %v3uint {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}}
; CHECK: OpStore %readvec [[phi_result]]
;CHECK-NOT: %38 = OpLoad %v3uint %37 Aligned 4
;CHECK-NOT: OpStore %readvec %38
;CHECK: {{%\w+}} = OpConvertPtrToU %ulong %37
;CHECK: {{%\w+}} = OpLoad %uint %gl_VertexIndex
;CHECK: {{%\w+}} = OpLoad %uint %gl_InstanceIndex
;CHECK: {{%\w+}} = OpCompositeConstruct %v4uint %uint_0 {{%\w+}} {{%\w+}} %uint_0
;CHECK: [[test_result:%\w+]] = OpFunctionCall %bool %)" + kFuncName + R"( %uint_23 %uint_67 {{%\w+}} {{%\w+}} %uint_12
;CHECK: OpSelectionMerge {{%\w+}} None
;CHECK: OpBranchConditional [[test_result]] {{%\w+}} {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: {{%\w+}} = OpLoad %v3uint %37 Aligned 4
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
;CHECK: [[phi_result:%\w+]] = OpPhi %v3uint {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}}
;CHECK: OpStore %readvec [[phi_result]]
OpBranch %13
%13 = OpLabel
%39 = OpLoad %int %i
@ -923,13 +758,13 @@ OpBranch %10
%12 = OpLabel
OpReturn
OpFunctionEnd
)" + kSearchAndTest + kStreamWrite3;
)";
// clang-format on
SetTargetEnv(SPV_ENV_VULKAN_1_2);
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ValidatorOptions()->scalar_block_layout = true;
SinglePassRunAndMatch<InstBuffAddrCheckPass>(text, true, 7, 23);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(text, true, 23);
}
} // namespace