opt: Remove bindless and buff addr instrumentation passes (#5657)

These were only used by Vulkan-Validation layers, but they
have been replaced by other code for several months.
This commit is contained in:
Jeremy Gebben 2024-05-02 16:52:17 -06:00 committed by GitHub
parent bfc3a15683
commit 9241a58a80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1 additions and 7525 deletions

View File

@ -128,8 +128,6 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/inline_pass.cpp \
source/opt/inline_exhaustive_pass.cpp \
source/opt/inline_opaque_pass.cpp \
source/opt/inst_bindless_check_pass.cpp \
source/opt/inst_buff_addr_check_pass.cpp \
source/opt/inst_debug_printf_pass.cpp \
source/opt/instruction.cpp \
source/opt/instruction_list.cpp \

View File

@ -681,10 +681,6 @@ static_library("spvtools_opt") {
"source/opt/inline_opaque_pass.h",
"source/opt/inline_pass.cpp",
"source/opt/inline_pass.h",
"source/opt/inst_bindless_check_pass.cpp",
"source/opt/inst_bindless_check_pass.h",
"source/opt/inst_buff_addr_check_pass.cpp",
"source/opt/inst_buff_addr_check_pass.h",
"source/opt/inst_debug_printf_pass.cpp",
"source/opt/inst_debug_printf_pass.h",
"source/opt/instruction.cpp",

View File

@ -22,8 +22,6 @@
// This file provides an external interface for applications that wish to
// communicate with shaders instrumented by passes created by:
//
// CreateInstBindlessCheckPass
// CreateInstBuffAddrCheckPass
// CreateInstDebugPrintfPass
//
// More detailed documentation of these routines can be found in optimizer.hpp
@ -34,17 +32,12 @@ namespace spvtools {
//
// The following values provide offsets into the output buffer struct
// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
// by InstBindlessCheckPass, InstBuffAddrCheckPass, and InstDebugPrintfPass.
// by InstDebugPrintfPass.
//
// The 1st member of the debug output buffer contains a set of flags
// controlling the behavior of instrumentation code.
static const int kDebugOutputFlagsOffset = 0;
// Values stored at kDebugOutputFlagsOffset
enum kInstFlags : unsigned int {
kInstBufferOOBEnable = 0x1,
};
// The 2nd member of the debug output buffer contains the next available word
// in the data stream to be written. Shaders will atomically read and update
// this value so as not to overwrite each others records. This value must be

View File

@ -747,53 +747,6 @@ Optimizer::PassToken CreateReduceLoadSizePass(
// them into a single instruction where possible.
Optimizer::PassToken CreateCombineAccessChainsPass();
// Create a pass to instrument bindless descriptor checking
// This pass instruments all bindless references to check that descriptor
// array indices are inbounds, and if the descriptor indexing extension is
// enabled, that the descriptor has been initialized. If the reference is
// invalid, a record is written to the debug output buffer (if space allows)
// and a null value is returned. This pass is designed to support bindless
// validation in the Vulkan validation layers.
//
// TODO(greg-lunarg): Add support for buffer references. Currently only does
// checking for image references.
//
// Dead code elimination should be run after this pass as the original,
// potentially invalid code is not removed and could cause undefined behavior,
// including crashes. It may also be beneficial to run Simplification
// (ie Constant Propagation), DeadBranchElim and BlockMerge after this pass to
// optimize instrument code involving the testing of compile-time constants.
// It is also generally recommended that this pass (and all
// instrumentation passes) be run after any legalization and optimization
// passes. This will give better analysis for the instrumentation and avoid
// potentially de-optimizing the instrument code, for example, inlining
// the debug record output function throughout the module.
//
// The instrumentation will write |shader_id| in each output record
// to identify the shader module which generated the record.
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
// all referenced bytes fall in a valid buffer. If the reference is
// invalid, a record is written to the debug output buffer (if space allows)
// and a null value is returned. This pass is designed to support buffer
// address validation in the Vulkan validation layers.
//
// Dead code elimination should be run after this pass as the original,
// potentially invalid code is not removed and could cause undefined behavior,
// including crashes. Instruction simplification would likely also be
// beneficial. It is also generally recommended that this pass (and all
// instrumentation passes) be run after any legalization and optimization
// passes. This will give better analysis for the instrumentation and avoid
// 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
// to identify the shader module which generated the record.
Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t shader_id);
// Create a pass to instrument OpDebugPrintf instructions.
// This pass replaces all OpDebugPrintf instructions with instructions to write
// a record containing the string id and the all specified values into a special

View File

@ -64,8 +64,6 @@ set(SPIRV_TOOLS_OPT_SOURCES
inline_exhaustive_pass.h
inline_opaque_pass.h
inline_pass.h
inst_bindless_check_pass.h
inst_buff_addr_check_pass.h
inst_debug_printf_pass.h
instruction.h
instruction_list.h
@ -186,8 +184,6 @@ set(SPIRV_TOOLS_OPT_SOURCES
inline_exhaustive_pass.cpp
inline_opaque_pass.cpp
inline_pass.cpp
inst_bindless_check_pass.cpp
inst_buff_addr_check_pass.cpp
inst_debug_printf_pass.cpp
instruction.cpp
instruction_list.cpp

View File

@ -1,761 +0,0 @@
// Copyright (c) 2018 The Khronos Group Inc.
// Copyright (c) 2018 Valve Corporation
// Copyright (c) 2018 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "inst_bindless_check_pass.h"
#include "source/spirv_constant.h"
namespace spvtools {
namespace opt {
namespace {
// Input Operand Indices
constexpr int kSpvImageSampleImageIdInIdx = 0;
constexpr int kSpvSampledImageImageIdInIdx = 0;
constexpr int kSpvSampledImageSamplerIdInIdx = 1;
constexpr int kSpvImageSampledImageIdInIdx = 0;
constexpr int kSpvCopyObjectOperandIdInIdx = 0;
constexpr int kSpvLoadPtrIdInIdx = 0;
constexpr int kSpvAccessChainBaseIdInIdx = 0;
constexpr int kSpvAccessChainIndex0IdInIdx = 1;
constexpr int kSpvTypeArrayTypeIdInIdx = 0;
constexpr int kSpvVariableStorageClassInIdx = 0;
constexpr int kSpvTypePtrTypeIdInIdx = 1;
constexpr int kSpvTypeImageDim = 1;
constexpr int kSpvTypeImageDepth = 2;
constexpr int kSpvTypeImageArrayed = 3;
constexpr int kSpvTypeImageMS = 4;
} // namespace
// This is a stub function for use with Import linkage
// clang-format off
// GLSL:
//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() {
enum {
kShaderId = 0,
kInstructionIndex = 1,
kStageInfo = 2,
kDescSet = 3,
kDescBinding = 4,
kDescIndex = 5,
kByteOffset = 6,
kNumArgs
};
if (check_desc_func_id_ != 0) {
return check_desc_func_id_;
}
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(kNumArgs, uint_type);
param_types[2] = v4uint_type;
const uint32_t func_id = TakeNextId();
std::unique_ptr<Function> func =
StartFunction(func_id, type_mgr->GetBoolType(), param_types);
func->SetFunctionEnd(EndFunction());
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);
check_desc_func_id_ = func_id;
// Make sure function doesn't get processed by
// InstrumentPass::InstProcessCallTreeFromRoots()
param2output_func_id_[3] = func_id;
return check_desc_func_id_;
}
// clang-format off
// GLSL:
// result = inst_bindless_check_desc(shader_id, inst_idx, stage_info, desc_set, binding, desc_idx, offset);
//
// clang-format on
uint32_t InstBindlessCheckPass::GenDescCheckCall(
uint32_t inst_idx, uint32_t stage_idx, uint32_t var_id,
uint32_t desc_idx_id, uint32_t offset_id, InstructionBuilder* builder) {
const uint32_t func_id = GenDescCheckFunctionId();
const std::vector<uint32_t> args = {
builder->GetUintConstantId(shader_id_),
builder->GetUintConstantId(inst_idx),
GenStageInfo(stage_idx, builder),
builder->GetUintConstantId(var2desc_set_[var_id]),
builder->GetUintConstantId(var2binding_[var_id]),
GenUintCastCode(desc_idx_id, builder),
offset_id};
return GenReadFunctionCall(GetBoolId(), func_id, args, builder);
}
uint32_t InstBindlessCheckPass::CloneOriginalImage(
uint32_t old_image_id, InstructionBuilder* builder) {
Instruction* new_image_inst;
Instruction* old_image_inst = get_def_use_mgr()->GetDef(old_image_id);
if (old_image_inst->opcode() == spv::Op::OpLoad) {
new_image_inst = builder->AddLoad(
old_image_inst->type_id(),
old_image_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
} else if (old_image_inst->opcode() == spv::Op::OpSampledImage) {
uint32_t clone_id = CloneOriginalImage(
old_image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx),
builder);
new_image_inst = builder->AddBinaryOp(
old_image_inst->type_id(), spv::Op::OpSampledImage, clone_id,
old_image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
} else if (old_image_inst->opcode() == spv::Op::OpImage) {
uint32_t clone_id = CloneOriginalImage(
old_image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx),
builder);
new_image_inst = builder->AddUnaryOp(old_image_inst->type_id(),
spv::Op::OpImage, clone_id);
} else {
assert(old_image_inst->opcode() == spv::Op::OpCopyObject &&
"expecting OpCopyObject");
uint32_t clone_id = CloneOriginalImage(
old_image_inst->GetSingleWordInOperand(kSpvCopyObjectOperandIdInIdx),
builder);
// Since we are cloning, no need to create new copy
new_image_inst = get_def_use_mgr()->GetDef(clone_id);
}
uid2offset_[new_image_inst->unique_id()] =
uid2offset_[old_image_inst->unique_id()];
uint32_t new_image_id = new_image_inst->result_id();
get_decoration_mgr()->CloneDecorations(old_image_id, new_image_id);
return new_image_id;
}
uint32_t InstBindlessCheckPass::CloneOriginalReference(
RefAnalysis* ref, InstructionBuilder* builder) {
// If original is image based, start by cloning descriptor load
uint32_t new_image_id = 0;
if (ref->desc_load_id != 0) {
uint32_t old_image_id =
ref->ref_inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
new_image_id = CloneOriginalImage(old_image_id, builder);
}
// Clone original reference
std::unique_ptr<Instruction> new_ref_inst(ref->ref_inst->Clone(context()));
uint32_t ref_result_id = ref->ref_inst->result_id();
uint32_t new_ref_id = 0;
if (ref_result_id != 0) {
new_ref_id = TakeNextId();
new_ref_inst->SetResultId(new_ref_id);
}
// Update new ref with new image if created
if (new_image_id != 0)
new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
// Register new reference and add to new block
Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
uid2offset_[added_inst->unique_id()] =
uid2offset_[ref->ref_inst->unique_id()];
if (new_ref_id != 0)
get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
return new_ref_id;
}
uint32_t InstBindlessCheckPass::GetImageId(Instruction* inst) {
switch (inst->opcode()) {
case spv::Op::OpImageSampleImplicitLod:
case spv::Op::OpImageSampleExplicitLod:
case spv::Op::OpImageSampleDrefImplicitLod:
case spv::Op::OpImageSampleDrefExplicitLod:
case spv::Op::OpImageSampleProjImplicitLod:
case spv::Op::OpImageSampleProjExplicitLod:
case spv::Op::OpImageSampleProjDrefImplicitLod:
case spv::Op::OpImageSampleProjDrefExplicitLod:
case spv::Op::OpImageGather:
case spv::Op::OpImageDrefGather:
case spv::Op::OpImageQueryLod:
case spv::Op::OpImageSparseSampleImplicitLod:
case spv::Op::OpImageSparseSampleExplicitLod:
case spv::Op::OpImageSparseSampleDrefImplicitLod:
case spv::Op::OpImageSparseSampleDrefExplicitLod:
case spv::Op::OpImageSparseSampleProjImplicitLod:
case spv::Op::OpImageSparseSampleProjExplicitLod:
case spv::Op::OpImageSparseSampleProjDrefImplicitLod:
case spv::Op::OpImageSparseSampleProjDrefExplicitLod:
case spv::Op::OpImageSparseGather:
case spv::Op::OpImageSparseDrefGather:
case spv::Op::OpImageFetch:
case spv::Op::OpImageRead:
case spv::Op::OpImageQueryFormat:
case spv::Op::OpImageQueryOrder:
case spv::Op::OpImageQuerySizeLod:
case spv::Op::OpImageQuerySize:
case spv::Op::OpImageQueryLevels:
case spv::Op::OpImageQuerySamples:
case spv::Op::OpImageSparseFetch:
case spv::Op::OpImageSparseRead:
case spv::Op::OpImageWrite:
return inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
default:
break;
}
return 0;
}
Instruction* InstBindlessCheckPass::GetPointeeTypeInst(Instruction* ptr_inst) {
uint32_t pte_ty_id = GetPointeeTypeId(ptr_inst);
return get_def_use_mgr()->GetDef(pte_ty_id);
}
bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
RefAnalysis* ref) {
ref->ref_inst = ref_inst;
if (ref_inst->opcode() == spv::Op::OpLoad ||
ref_inst->opcode() == spv::Op::OpStore) {
ref->desc_load_id = 0;
ref->ptr_id = ref_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
if (ptr_inst->opcode() != spv::Op::OpAccessChain) return false;
ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
if (var_inst->opcode() != spv::Op::OpVariable) return false;
spv::StorageClass storage_class = spv::StorageClass(
var_inst->GetSingleWordInOperand(kSpvVariableStorageClassInIdx));
switch (storage_class) {
case spv::StorageClass::Uniform:
case spv::StorageClass::StorageBuffer:
break;
default:
return false;
break;
}
// Check for deprecated storage block form
if (storage_class == spv::StorageClass::Uniform) {
uint32_t var_ty_id = var_inst->type_id();
Instruction* var_ty_inst = get_def_use_mgr()->GetDef(var_ty_id);
uint32_t ptr_ty_id =
var_ty_inst->GetSingleWordInOperand(kSpvTypePtrTypeIdInIdx);
Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id);
spv::Op ptr_ty_op = ptr_ty_inst->opcode();
uint32_t block_ty_id =
(ptr_ty_op == spv::Op::OpTypeArray ||
ptr_ty_op == spv::Op::OpTypeRuntimeArray)
? ptr_ty_inst->GetSingleWordInOperand(kSpvTypeArrayTypeIdInIdx)
: ptr_ty_id;
assert(get_def_use_mgr()->GetDef(block_ty_id)->opcode() ==
spv::Op::OpTypeStruct &&
"unexpected block type");
bool block_found = get_decoration_mgr()->FindDecoration(
block_ty_id, uint32_t(spv::Decoration::Block),
[](const Instruction&) { return true; });
if (!block_found) {
// If block decoration not found, verify deprecated form of SSBO
bool buffer_block_found = get_decoration_mgr()->FindDecoration(
block_ty_id, uint32_t(spv::Decoration::BufferBlock),
[](const Instruction&) { return true; });
USE_ASSERT(buffer_block_found && "block decoration not found");
storage_class = spv::StorageClass::StorageBuffer;
}
}
ref->strg_class = uint32_t(storage_class);
Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
switch (desc_type_inst->opcode()) {
case spv::Op::OpTypeArray:
case spv::Op::OpTypeRuntimeArray:
// A load through a descriptor array will have at least 3 operands. We
// do not want to instrument loads of descriptors here which are part of
// an image-based reference.
if (ptr_inst->NumInOperands() < 3) return false;
ref->desc_idx_id =
ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
break;
default:
break;
}
auto decos =
context()->get_decoration_mgr()->GetDecorationsFor(ref->var_id, false);
for (const auto& deco : decos) {
spv::Decoration d = spv::Decoration(deco->GetSingleWordInOperand(1u));
if (d == spv::Decoration::DescriptorSet) {
ref->set = deco->GetSingleWordInOperand(2u);
} else if (d == spv::Decoration::Binding) {
ref->binding = deco->GetSingleWordInOperand(2u);
}
}
return true;
}
// Reference is not load or store. If not an image-based reference, return.
ref->image_id = GetImageId(ref_inst);
if (ref->image_id == 0) return false;
// Search for descriptor load
uint32_t desc_load_id = ref->image_id;
Instruction* desc_load_inst;
for (;;) {
desc_load_inst = get_def_use_mgr()->GetDef(desc_load_id);
if (desc_load_inst->opcode() == spv::Op::OpSampledImage)
desc_load_id =
desc_load_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
else if (desc_load_inst->opcode() == spv::Op::OpImage)
desc_load_id =
desc_load_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
else if (desc_load_inst->opcode() == spv::Op::OpCopyObject)
desc_load_id =
desc_load_inst->GetSingleWordInOperand(kSpvCopyObjectOperandIdInIdx);
else
break;
}
if (desc_load_inst->opcode() != spv::Op::OpLoad) {
// TODO(greg-lunarg): Handle additional possibilities?
return false;
}
ref->desc_load_id = desc_load_id;
ref->ptr_id = desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
if (ptr_inst->opcode() == spv::Op::OpVariable) {
ref->desc_idx_id = 0;
ref->var_id = ref->ptr_id;
} else if (ptr_inst->opcode() == spv::Op::OpAccessChain) {
if (ptr_inst->NumInOperands() != 2) {
assert(false && "unexpected bindless index number");
return false;
}
ref->desc_idx_id =
ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
if (var_inst->opcode() != spv::Op::OpVariable) {
assert(false && "unexpected bindless base");
return false;
}
} else {
// TODO(greg-lunarg): Handle additional possibilities?
return false;
}
auto decos =
context()->get_decoration_mgr()->GetDecorationsFor(ref->var_id, false);
for (const auto& deco : decos) {
spv::Decoration d = spv::Decoration(deco->GetSingleWordInOperand(1u));
if (d == spv::Decoration::DescriptorSet) {
ref->set = deco->GetSingleWordInOperand(2u);
} else if (d == spv::Decoration::Binding) {
ref->binding = deco->GetSingleWordInOperand(2u);
}
}
return true;
}
uint32_t InstBindlessCheckPass::FindStride(uint32_t ty_id,
uint32_t stride_deco) {
uint32_t stride = 0xdeadbeef;
bool found = get_decoration_mgr()->FindDecoration(
ty_id, stride_deco, [&stride](const Instruction& deco_inst) {
stride = deco_inst.GetSingleWordInOperand(2u);
return true;
});
USE_ASSERT(found && "stride not found");
return stride;
}
uint32_t InstBindlessCheckPass::ByteSize(uint32_t ty_id, uint32_t matrix_stride,
bool col_major, bool in_matrix) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
const analysis::Type* sz_ty = type_mgr->GetType(ty_id);
if (sz_ty->kind() == analysis::Type::kPointer) {
// Assuming PhysicalStorageBuffer pointer
return 8;
}
if (sz_ty->kind() == analysis::Type::kMatrix) {
assert(matrix_stride != 0 && "missing matrix stride");
const analysis::Matrix* m_ty = sz_ty->AsMatrix();
if (col_major) {
return m_ty->element_count() * matrix_stride;
} else {
const analysis::Vector* v_ty = m_ty->element_type()->AsVector();
return v_ty->element_count() * matrix_stride;
}
}
uint32_t size = 1;
if (sz_ty->kind() == analysis::Type::kVector) {
const analysis::Vector* v_ty = sz_ty->AsVector();
size = v_ty->element_count();
const analysis::Type* comp_ty = v_ty->element_type();
// if vector in row major matrix, the vector is strided so return the
// number of bytes spanned by the vector
if (in_matrix && !col_major && matrix_stride > 0) {
uint32_t comp_ty_id = type_mgr->GetId(comp_ty);
return (size - 1) * matrix_stride + ByteSize(comp_ty_id, 0, false, false);
}
sz_ty = comp_ty;
}
switch (sz_ty->kind()) {
case analysis::Type::kFloat: {
const analysis::Float* f_ty = sz_ty->AsFloat();
size *= f_ty->width();
} break;
case analysis::Type::kInteger: {
const analysis::Integer* i_ty = sz_ty->AsInteger();
size *= i_ty->width();
} break;
default: { assert(false && "unexpected type"); } break;
}
size /= 8;
return size;
}
uint32_t InstBindlessCheckPass::GenLastByteIdx(RefAnalysis* ref,
InstructionBuilder* builder) {
// Find outermost buffer type and its access chain index
Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
Instruction* desc_ty_inst = GetPointeeTypeInst(var_inst);
uint32_t buff_ty_id;
uint32_t ac_in_idx = 1;
switch (desc_ty_inst->opcode()) {
case spv::Op::OpTypeArray:
case spv::Op::OpTypeRuntimeArray:
buff_ty_id = desc_ty_inst->GetSingleWordInOperand(0);
++ac_in_idx;
break;
default:
assert(desc_ty_inst->opcode() == spv::Op::OpTypeStruct &&
"unexpected descriptor type");
buff_ty_id = desc_ty_inst->result_id();
break;
}
// Process remaining access chain indices
Instruction* ac_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
uint32_t curr_ty_id = buff_ty_id;
uint32_t sum_id = 0u;
uint32_t matrix_stride = 0u;
bool col_major = false;
uint32_t matrix_stride_id = 0u;
bool in_matrix = false;
while (ac_in_idx < ac_inst->NumInOperands()) {
uint32_t curr_idx_id = ac_inst->GetSingleWordInOperand(ac_in_idx);
Instruction* curr_ty_inst = get_def_use_mgr()->GetDef(curr_ty_id);
uint32_t curr_offset_id = 0;
switch (curr_ty_inst->opcode()) {
case spv::Op::OpTypeArray:
case spv::Op::OpTypeRuntimeArray: {
// Get array stride and multiply by current index
uint32_t arr_stride =
FindStride(curr_ty_id, uint32_t(spv::Decoration::ArrayStride));
uint32_t arr_stride_id = builder->GetUintConstantId(arr_stride);
uint32_t curr_idx_32b_id = Gen32BitCvtCode(curr_idx_id, builder);
Instruction* curr_offset_inst = builder->AddBinaryOp(
GetUintId(), spv::Op::OpIMul, arr_stride_id, curr_idx_32b_id);
curr_offset_id = curr_offset_inst->result_id();
// Get element type for next step
curr_ty_id = curr_ty_inst->GetSingleWordInOperand(0);
} break;
case spv::Op::OpTypeMatrix: {
assert(matrix_stride != 0 && "missing matrix stride");
matrix_stride_id = builder->GetUintConstantId(matrix_stride);
uint32_t vec_ty_id = curr_ty_inst->GetSingleWordInOperand(0);
// If column major, multiply column index by matrix stride, otherwise
// by vector component size and save matrix stride for vector (row)
// index
uint32_t col_stride_id;
if (col_major) {
col_stride_id = matrix_stride_id;
} else {
Instruction* vec_ty_inst = get_def_use_mgr()->GetDef(vec_ty_id);
uint32_t comp_ty_id = vec_ty_inst->GetSingleWordInOperand(0u);
uint32_t col_stride = ByteSize(comp_ty_id, 0u, false, false);
col_stride_id = builder->GetUintConstantId(col_stride);
}
uint32_t curr_idx_32b_id = Gen32BitCvtCode(curr_idx_id, builder);
Instruction* curr_offset_inst = builder->AddBinaryOp(
GetUintId(), spv::Op::OpIMul, col_stride_id, curr_idx_32b_id);
curr_offset_id = curr_offset_inst->result_id();
// Get element type for next step
curr_ty_id = vec_ty_id;
in_matrix = true;
} break;
case spv::Op::OpTypeVector: {
// If inside a row major matrix type, multiply index by matrix stride,
// else multiply by component size
uint32_t comp_ty_id = curr_ty_inst->GetSingleWordInOperand(0u);
uint32_t curr_idx_32b_id = Gen32BitCvtCode(curr_idx_id, builder);
if (in_matrix && !col_major) {
Instruction* curr_offset_inst = builder->AddBinaryOp(
GetUintId(), spv::Op::OpIMul, matrix_stride_id, curr_idx_32b_id);
curr_offset_id = curr_offset_inst->result_id();
} else {
uint32_t comp_ty_sz = ByteSize(comp_ty_id, 0u, false, false);
uint32_t comp_ty_sz_id = builder->GetUintConstantId(comp_ty_sz);
Instruction* curr_offset_inst = builder->AddBinaryOp(
GetUintId(), spv::Op::OpIMul, comp_ty_sz_id, curr_idx_32b_id);
curr_offset_id = curr_offset_inst->result_id();
}
// Get element type for next step
curr_ty_id = comp_ty_id;
} break;
case spv::Op::OpTypeStruct: {
// Get buffer byte offset for the referenced member
Instruction* curr_idx_inst = get_def_use_mgr()->GetDef(curr_idx_id);
assert(curr_idx_inst->opcode() == spv::Op::OpConstant &&
"unexpected struct index");
uint32_t member_idx = curr_idx_inst->GetSingleWordInOperand(0);
uint32_t member_offset = 0xdeadbeef;
bool found = get_decoration_mgr()->FindDecoration(
curr_ty_id, uint32_t(spv::Decoration::Offset),
[&member_idx, &member_offset](const Instruction& deco_inst) {
if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
return false;
member_offset = deco_inst.GetSingleWordInOperand(3u);
return true;
});
USE_ASSERT(found && "member offset not found");
curr_offset_id = builder->GetUintConstantId(member_offset);
// Look for matrix stride for this member if there is one. The matrix
// stride is not on the matrix type, but in a OpMemberDecorate on the
// enclosing struct type at the member index. If none found, reset
// stride to 0.
found = get_decoration_mgr()->FindDecoration(
curr_ty_id, uint32_t(spv::Decoration::MatrixStride),
[&member_idx, &matrix_stride](const Instruction& deco_inst) {
if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
return false;
matrix_stride = deco_inst.GetSingleWordInOperand(3u);
return true;
});
if (!found) matrix_stride = 0;
// Look for column major decoration
found = get_decoration_mgr()->FindDecoration(
curr_ty_id, uint32_t(spv::Decoration::ColMajor),
[&member_idx, &col_major](const Instruction& deco_inst) {
if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
return false;
col_major = true;
return true;
});
if (!found) col_major = false;
// Get element type for next step
curr_ty_id = curr_ty_inst->GetSingleWordInOperand(member_idx);
} break;
default: { assert(false && "unexpected non-composite type"); } break;
}
if (sum_id == 0)
sum_id = curr_offset_id;
else {
Instruction* sum_inst =
builder->AddIAdd(GetUintId(), sum_id, curr_offset_id);
sum_id = sum_inst->result_id();
}
++ac_in_idx;
}
// Add in offset of last byte of referenced object
uint32_t bsize = ByteSize(curr_ty_id, matrix_stride, col_major, in_matrix);
uint32_t last = bsize - 1;
uint32_t last_id = builder->GetUintConstantId(last);
Instruction* sum_inst = builder->AddIAdd(GetUintId(), sum_id, last_id);
return sum_inst->result_id();
}
void InstBindlessCheckPass::GenCheckCode(
uint32_t check_id, RefAnalysis* ref,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
BasicBlock* back_blk_ptr = &*new_blocks->back();
InstructionBuilder builder(
context(), back_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
// Gen conditional branch on check_id. Valid branch generates original
// reference. Invalid generates debug output and zero result (if needed).
uint32_t merge_blk_id = TakeNextId();
uint32_t valid_blk_id = TakeNextId();
uint32_t invalid_blk_id = TakeNextId();
std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
(void)builder.AddConditionalBranch(
check_id, valid_blk_id, invalid_blk_id, merge_blk_id,
uint32_t(spv::SelectionControlMask::MaskNone));
// Gen valid bounds branch
std::unique_ptr<BasicBlock> new_blk_ptr(
new BasicBlock(std::move(valid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
uint32_t new_ref_id = CloneOriginalReference(ref, &builder);
uint32_t null_id = 0;
uint32_t ref_type_id = ref->ref_inst->type_id();
(void)builder.AddBranch(merge_blk_id);
new_blocks->push_back(std::move(new_blk_ptr));
// Gen invalid block
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// 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();
analysis::Type* ref_type = type_mgr->GetType(ref_type_id);
if (ref_type->AsPointer() != nullptr) {
context()->AddCapability(spv::Capability::Int64);
uint32_t null_u64_id = GetNullId(GetUint64Id());
Instruction* null_ptr_inst = builder.AddUnaryOp(
ref_type_id, spv::Op::OpConvertUToPtr, null_u64_id);
null_id = null_ptr_inst->result_id();
} else {
null_id = GetNullId(ref_type_id);
}
}
// Remember last invalid block id
uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
// Gen zero for invalid reference
(void)builder.AddBranch(merge_blk_id);
new_blocks->push_back(std::move(new_blk_ptr));
// Gen merge block
new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// Gen phi of new reference and zero, if necessary, and replace the
// result id of the original reference with that of the Phi. Kill original
// reference.
if (new_ref_id != 0) {
Instruction* phi_inst = builder.AddPhi(
ref_type_id, {new_ref_id, valid_blk_id, null_id, last_invalid_blk_id});
context()->ReplaceAllUsesWith(ref->ref_inst->result_id(),
phi_inst->result_id());
}
new_blocks->push_back(std::move(new_blk_ptr));
context()->KillInst(ref->ref_inst);
}
void InstBindlessCheckPass::GenDescCheckCode(
BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
// Look for reference through descriptor. If not, return.
RefAnalysis ref;
if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
std::unique_ptr<BasicBlock> new_blk_ptr;
// Move original block's preceding instructions into first new block
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
InstructionBuilder builder(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
new_blocks->push_back(std::move(new_blk_ptr));
// Determine if we can only do initialization check
uint32_t ref_id = builder.GetUintConstantId(0u);
spv::Op op = ref.ref_inst->opcode();
if (ref.desc_load_id != 0) {
uint32_t num_in_oprnds = ref.ref_inst->NumInOperands();
if ((op == spv::Op::OpImageRead && num_in_oprnds == 2) ||
(op == spv::Op::OpImageFetch && num_in_oprnds == 2) ||
(op == spv::Op::OpImageWrite && num_in_oprnds == 3)) {
Instruction* image_inst = get_def_use_mgr()->GetDef(ref.image_id);
uint32_t image_ty_id = image_inst->type_id();
Instruction* image_ty_inst = get_def_use_mgr()->GetDef(image_ty_id);
if (spv::Dim(image_ty_inst->GetSingleWordInOperand(kSpvTypeImageDim)) ==
spv::Dim::Buffer) {
if ((image_ty_inst->GetSingleWordInOperand(kSpvTypeImageDepth) == 0) &&
(image_ty_inst->GetSingleWordInOperand(kSpvTypeImageArrayed) ==
0) &&
(image_ty_inst->GetSingleWordInOperand(kSpvTypeImageMS) == 0)) {
ref_id = GenUintCastCode(ref.ref_inst->GetSingleWordInOperand(1),
&builder);
}
}
}
} else {
// For now, only do bounds check for non-aggregate types. Otherwise
// just do descriptor initialization check.
// TODO(greg-lunarg): Do bounds check for aggregate loads and stores
Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst);
spv::Op pte_type_op = pte_type_inst->opcode();
if (pte_type_op != spv::Op::OpTypeArray &&
pte_type_op != spv::Op::OpTypeRuntimeArray &&
pte_type_op != spv::Op::OpTypeStruct) {
ref_id = GenLastByteIdx(&ref, &builder);
}
}
// Read initialization/bounds from debug input buffer. If index id not yet
// set, binding is single descriptor, so set index to constant 0.
if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
uint32_t check_id =
GenDescCheckCall(ref.ref_inst->unique_id(), stage_idx, ref.var_id,
ref.desc_idx_id, ref_id, &builder);
// Generate runtime initialization/bounds test code with true branch
// being full reference and false branch being zero
// for the referenced value.
GenCheckCode(check_id, &ref, new_blocks);
// Move original block's remaining code into remainder/merge block and add
// to new blocks
BasicBlock* back_blk_ptr = &*new_blocks->back();
MovePostludeCode(ref_block_itr, back_blk_ptr);
}
void InstBindlessCheckPass::InitializeInstBindlessCheck() {
// Initialize base class
InitializeInstrument();
for (auto& anno : get_module()->annotations()) {
if (anno.opcode() == spv::Op::OpDecorate) {
if (spv::Decoration(anno.GetSingleWordInOperand(1u)) ==
spv::Decoration::DescriptorSet) {
var2desc_set_[anno.GetSingleWordInOperand(0u)] =
anno.GetSingleWordInOperand(2u);
} else if (spv::Decoration(anno.GetSingleWordInOperand(1u)) ==
spv::Decoration::Binding) {
var2binding_[anno.GetSingleWordInOperand(0u)] =
anno.GetSingleWordInOperand(2u);
}
}
}
}
Pass::Status InstBindlessCheckPass::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::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,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
return GenDescCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
new_blocks);
};
InstProcessEntryPointCallTree(pfn);
// This pass always changes the memory model, so that linking will work
// properly.
return Status::SuccessWithChange;
}
Pass::Status InstBindlessCheckPass::Process() {
InitializeInstBindlessCheck();
return ProcessImpl();
}
} // namespace opt
} // namespace spvtools

View File

@ -1,130 +0,0 @@
// Copyright (c) 2018 The Khronos Group Inc.
// Copyright (c) 2018 Valve Corporation
// Copyright (c) 2018 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
#define LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
#include "instrument_pass.h"
namespace spvtools {
namespace opt {
// This class/pass is designed to support the bindless (descriptor indexing)
// GPU-assisted validation layer of
// https://github.com/KhronosGroup/Vulkan-ValidationLayers. Its internal and
// external design may change as the layer evolves.
class InstBindlessCheckPass : public InstrumentPass {
public:
InstBindlessCheckPass(uint32_t shader_id)
: InstrumentPass(0, shader_id, true, true) {}
~InstBindlessCheckPass() override = default;
// See optimizer.hpp for pass user documentation.
Status Process() override;
const char* name() const override { return "inst-bindless-check-pass"; }
private:
void GenDescCheckCode(BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr,
uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
uint32_t GenDescCheckFunctionId();
uint32_t GenDescCheckCall(uint32_t inst_idx, uint32_t stage_idx,
uint32_t var_id, uint32_t index_id,
uint32_t byte_offset, InstructionBuilder* builder);
// Analysis data for descriptor reference components, generated by
// AnalyzeDescriptorReference. It is necessary and sufficient for further
// analysis and regeneration of the reference.
typedef struct RefAnalysis {
uint32_t desc_load_id{0};
uint32_t image_id{0};
uint32_t load_id{0};
uint32_t ptr_id{0};
uint32_t var_id{0};
uint32_t set{0};
uint32_t binding{0};
uint32_t desc_idx_id{0};
uint32_t strg_class{0};
Instruction* ref_inst{nullptr};
} RefAnalysis;
// Return size of type |ty_id| in bytes. Use |matrix_stride| and |col_major|
// for matrix type, or for vector type if vector is |in_matrix|.
uint32_t ByteSize(uint32_t ty_id, uint32_t matrix_stride, bool col_major,
bool in_matrix);
// Return stride of type |ty_id| with decoration |stride_deco|. Return 0
// if not found
uint32_t FindStride(uint32_t ty_id, uint32_t stride_deco);
// Generate index of last byte referenced by buffer reference |ref|
uint32_t GenLastByteIdx(RefAnalysis* ref, InstructionBuilder* builder);
// Clone original image computation starting at |image_id| into |builder|.
// This may generate more than one instruction if necessary.
uint32_t CloneOriginalImage(uint32_t image_id, InstructionBuilder* builder);
// Clone original original reference encapsulated by |ref| into |builder|.
// This may generate more than one instruction if necessary.
uint32_t CloneOriginalReference(RefAnalysis* ref,
InstructionBuilder* builder);
// If |inst| references through an image, return the id of the image it
// references through. Else return 0.
uint32_t GetImageId(Instruction* inst);
// Get pointee type inst of pointer value |ptr_inst|.
Instruction* GetPointeeTypeInst(Instruction* ptr_inst);
// Analyze descriptor reference |ref_inst| and save components into |ref|.
// Return true if |ref_inst| is a descriptor reference, false otherwise.
bool AnalyzeDescriptorReference(Instruction* ref_inst, RefAnalysis* ref);
// Generate instrumentation code for generic test result |check_id|, starting
// with |builder| of block |new_blk_ptr|, adding new blocks to |new_blocks|.
// Generate conditional branch to a valid or invalid branch. Generate valid
// block which does original reference |ref|. Generate invalid block which
// 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, RefAnalysis* ref,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
// Initialize state for instrumenting bindless checking
void InitializeInstBindlessCheck();
// Apply GenDescIdxCheckCode to every instruction in module. Then apply
// GenDescInitCheckCode to every instruction in module.
Pass::Status ProcessImpl();
// Mapping from variable to descriptor set
std::unordered_map<uint32_t, uint32_t> var2desc_set_;
// Mapping from variable to binding
std::unordered_map<uint32_t, uint32_t> var2binding_;
uint32_t check_desc_func_id_{0};
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_

View File

@ -1,331 +0,0 @@
// Copyright (c) 2019 The Khronos Group Inc.
// Copyright (c) 2019 Valve Corporation
// Copyright (c) 2019 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "inst_buff_addr_check_pass.h"
namespace spvtools {
namespace opt {
uint32_t InstBuffAddrCheckPass::CloneOriginalReference(
Instruction* ref_inst, InstructionBuilder* builder) {
// Clone original ref with new result id (if load)
assert((ref_inst->opcode() == spv::Op::OpLoad ||
ref_inst->opcode() == spv::Op::OpStore) &&
"unexpected ref");
std::unique_ptr<Instruction> new_ref_inst(ref_inst->Clone(context()));
uint32_t ref_result_id = ref_inst->result_id();
uint32_t new_ref_id = 0;
if (ref_result_id != 0) {
new_ref_id = TakeNextId();
new_ref_inst->SetResultId(new_ref_id);
}
// Register new reference and add to new block
Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
uid2offset_[added_inst->unique_id()] = uid2offset_[ref_inst->unique_id()];
if (new_ref_id != 0)
get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
return new_ref_id;
}
bool InstBuffAddrCheckPass::IsPhysicalBuffAddrReference(Instruction* ref_inst) {
if (ref_inst->opcode() != spv::Op::OpLoad &&
ref_inst->opcode() != spv::Op::OpStore)
return false;
uint32_t ptr_id = ref_inst->GetSingleWordInOperand(0);
analysis::DefUseManager* du_mgr = get_def_use_mgr();
Instruction* ptr_inst = du_mgr->GetDef(ptr_id);
if (ptr_inst->opcode() != spv::Op::OpAccessChain) return false;
uint32_t ptr_ty_id = ptr_inst->type_id();
Instruction* ptr_ty_inst = du_mgr->GetDef(ptr_ty_id);
if (spv::StorageClass(ptr_ty_inst->GetSingleWordInOperand(0)) !=
spv::StorageClass::PhysicalStorageBufferEXT)
return false;
return true;
}
// TODO(greg-lunarg): Refactor with InstBindlessCheckPass::GenCheckCode() ??
void InstBuffAddrCheckPass::GenCheckCode(
uint32_t check_id, Instruction* ref_inst,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
BasicBlock* back_blk_ptr = &*new_blocks->back();
InstructionBuilder builder(
context(), back_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
// Gen conditional branch on check_id. Valid branch generates original
// reference. Invalid generates debug output and zero result (if needed).
uint32_t merge_blk_id = TakeNextId();
uint32_t valid_blk_id = TakeNextId();
uint32_t invalid_blk_id = TakeNextId();
std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
(void)builder.AddConditionalBranch(
check_id, valid_blk_id, invalid_blk_id, merge_blk_id,
uint32_t(spv::SelectionControlMask::MaskNone));
// Gen valid branch
std::unique_ptr<BasicBlock> new_blk_ptr(
new BasicBlock(std::move(valid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
uint32_t new_ref_id = CloneOriginalReference(ref_inst, &builder);
(void)builder.AddBranch(merge_blk_id);
new_blocks->push_back(std::move(new_blk_ptr));
// Gen invalid block
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// 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;
if (new_ref_id != 0) {
uint32_t ref_type_id = ref_inst->type_id();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Type* ref_type = type_mgr->GetType(ref_type_id);
if (ref_type->AsPointer() != nullptr) {
uint32_t null_u64_id = GetNullId(GetUint64Id());
Instruction* null_ptr_inst = builder.AddUnaryOp(
ref_type_id, spv::Op::OpConvertUToPtr, null_u64_id);
null_id = null_ptr_inst->result_id();
} else {
null_id = GetNullId(ref_type_id);
}
}
(void)builder.AddBranch(merge_blk_id);
new_blocks->push_back(std::move(new_blk_ptr));
// Gen merge block
new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// Gen phi of new reference and zero, if necessary, and replace the
// result id of the original reference with that of the Phi. Kill original
// reference.
if (new_ref_id != 0) {
Instruction* phi_inst =
builder.AddPhi(ref_inst->type_id(),
{new_ref_id, valid_blk_id, null_id, invalid_blk_id});
context()->ReplaceAllUsesWith(ref_inst->result_id(), phi_inst->result_id());
}
new_blocks->push_back(std::move(new_blk_ptr));
context()->KillInst(ref_inst);
}
uint32_t InstBuffAddrCheckPass::GetTypeLength(uint32_t type_id) {
Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
switch (type_inst->opcode()) {
case spv::Op::OpTypeFloat:
case spv::Op::OpTypeInt:
return type_inst->GetSingleWordInOperand(0) / 8u;
case spv::Op::OpTypeVector:
case spv::Op::OpTypeMatrix:
return type_inst->GetSingleWordInOperand(1) *
GetTypeLength(type_inst->GetSingleWordInOperand(0));
case spv::Op::OpTypePointer:
assert(spv::StorageClass(type_inst->GetSingleWordInOperand(0)) ==
spv::StorageClass::PhysicalStorageBufferEXT &&
"unexpected pointer type");
return 8u;
case spv::Op::OpTypeArray: {
uint32_t const_id = type_inst->GetSingleWordInOperand(1);
Instruction* const_inst = get_def_use_mgr()->GetDef(const_id);
uint32_t cnt = const_inst->GetSingleWordInOperand(0);
return cnt * GetTypeLength(type_inst->GetSingleWordInOperand(0));
}
case spv::Op::OpTypeStruct: {
// Figure out the location of the last byte of the last member of the
// structure.
uint32_t last_offset = 0, last_len = 0;
get_decoration_mgr()->ForEachDecoration(
type_id, uint32_t(spv::Decoration::Offset),
[&last_offset](const Instruction& deco_inst) {
last_offset = deco_inst.GetSingleWordInOperand(3);
});
type_inst->ForEachInId([&last_len, this](const uint32_t* iid) {
last_len = GetTypeLength(*iid);
});
return last_offset + last_len;
}
case spv::Op::OpTypeRuntimeArray:
default:
assert(false && "unexpected type");
return 0;
}
}
void InstBuffAddrCheckPass::AddParam(uint32_t type_id,
std::vector<uint32_t>* param_vec,
std::unique_ptr<Function>* input_func) {
uint32_t pid = TakeNextId();
param_vec->push_back(pid);
std::unique_ptr<Instruction> param_inst(new Instruction(
get_module()->context(), spv::Op::OpFunctionParameter, type_id, pid, {}));
get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
(*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() {
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 stage_idx) {
// Enable Int64 if necessary
// Convert reference pointer to uint64
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);
const uint32_t ref_ptr_ty_id = ref_ptr_inst->type_id();
Instruction* ref_ptr_ty_inst = du_mgr->GetDef(ref_ptr_ty_id);
const uint32_t ref_len =
GetTypeLength(ref_ptr_ty_inst->GetSingleWordInOperand(1));
// Gen call to search and test function
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(
BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
// Look for reference through indexed descriptor. If found, analyze and
// save components. If not, return.
Instruction* ref_inst = &*ref_inst_itr;
if (!IsPhysicalBuffAddrReference(ref_inst)) return;
// Move original block's preceding instructions into first new block
std::unique_ptr<BasicBlock> new_blk_ptr;
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
InstructionBuilder builder(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
new_blocks->push_back(std::move(new_blk_ptr));
// 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, 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, 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();
MovePostludeCode(ref_block_itr, back_blk_ptr);
}
void InstBuffAddrCheckPass::InitInstBuffAddrCheck() {
// Initialize base class
InitializeInstrument();
// Initialize class
search_test_func_id_ = 0;
}
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::PhysicalStorageBufferAddresses);
Instruction* memory_model = get_module()->GetMemoryModel();
memory_model->SetInOperand(
0u, {uint32_t(spv::AddressingModel::PhysicalStorageBuffer64)});
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,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
return GenBuffAddrCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
new_blocks);
};
InstProcessEntryPointCallTree(pfn);
// This pass always changes the memory model, so that linking will work
// properly.
return Status::SuccessWithChange;
}
Pass::Status InstBuffAddrCheckPass::Process() {
InitInstBuffAddrCheck();
return ProcessImpl();
}
} // namespace opt
} // namespace spvtools

View File

@ -1,130 +0,0 @@
// Copyright (c) 2019 The Khronos Group Inc.
// Copyright (c) 2019 Valve Corporation
// Copyright (c) 2019 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef LIBSPIRV_OPT_INST_BUFFER_ADDRESS_PASS_H_
#define LIBSPIRV_OPT_INST_BUFFER_ADDRESS_PASS_H_
#include "instrument_pass.h"
namespace spvtools {
namespace opt {
// This class/pass is designed to support the GPU-assisted validation layer of
// the Buffer Device Address (BDA) extension in
// https://github.com/KhronosGroup/Vulkan-ValidationLayers. The internal and
// external design of this class may change as the layer evolves.
class InstBuffAddrCheckPass : public InstrumentPass {
public:
// For test harness only
InstBuffAddrCheckPass() : InstrumentPass(0, 23, false, true) {}
// For all other interfaces
InstBuffAddrCheckPass(uint32_t shader_id)
: InstrumentPass(0, shader_id, false, true) {}
~InstBuffAddrCheckPass() override = default;
// See optimizer.hpp for pass user documentation.
Status Process() override;
const char* name() const override { return "inst-buff-addr-check-pass"; }
private:
// Return byte length of type |type_id|. Must be int, float, vector, matrix,
// struct, array or physical pointer. Uses std430 alignment and sizes.
uint32_t GetTypeLength(uint32_t type_id);
// Add |type_id| param to |input_func| and add id to |param_vec|.
void AddParam(uint32_t type_id, std::vector<uint32_t>* param_vec,
std::unique_ptr<Function>* input_func);
// Return id for search and test function. Generate it if not already gen'd.
uint32_t GetSearchAndTestFuncId();
// Generate code into |builder| to do search of the BDA debug input buffer
// for the buffer used by |ref_inst| and test that all bytes of reference
// 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 stage_idx);
// This function does checking instrumentation on a single
// instruction which references through a physical storage buffer address.
// GenBuffAddrCheckCode generates code that checks that all bytes that
// are referenced fall within a buffer that was queried via
// the Vulkan API call vkGetBufferDeviceAddressEXT().
//
// The function is designed to be passed to
// InstrumentPass::InstProcessEntryPointCallTree(), which applies the
// function to each instruction in a module and replaces the instruction
// with instrumented code if warranted.
//
// If |ref_inst_itr| is a physical storage buffer reference, return in
// |new_blocks| the result of instrumenting it with validation code within
// its block at |ref_block_itr|. The validation code first executes a check
// for the specific condition called for. If the check passes, it executes
// the remainder of the reference, otherwise writes a record to the debug
// output buffer stream including |function_idx, instruction_idx, stage_idx|
// and replaces the reference with the null value of the original type. The
// block at |ref_block_itr| can just be replaced with the blocks in
// |new_blocks|, which will contain at least two blocks. The last block will
// comprise all instructions following |ref_inst_itr|,
// preceded by a phi instruction if needed.
//
// This instrumentation function utilizes GenDebugStreamWrite() to write its
// error records. The validation-specific part of the error record will
// have the format:
//
// Validation Error Code (=kInstErrorBuffAddr)
// Buffer Address (lowest 32 bits)
// Buffer Address (highest 32 bits)
//
void GenBuffAddrCheckCode(
BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
// Return true if |ref_inst| is a physical buffer address reference, false
// otherwise.
bool IsPhysicalBuffAddrReference(Instruction* ref_inst);
// Clone original reference |ref_inst| into |builder| and return id of result
uint32_t CloneOriginalReference(Instruction* ref_inst,
InstructionBuilder* builder);
// Generate instrumentation code for boolean test result |check_id|,
// adding new blocks to |new_blocks|. Generate conditional branch to valid
// or invalid reference blocks. Generate valid reference block which does
// original reference |ref_inst|. Then generate invalid reference block which
// 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, Instruction* ref_inst,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
// Initialize state for instrumenting physical buffer address checking
void InitInstBuffAddrCheck();
// Apply GenBuffAddrCheckCode to every instruction in module.
Pass::Status ProcessImpl();
// Id of search and test function, if already gen'd, else zero.
uint32_t search_test_func_id_;
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_INST_BUFFER_ADDRESS_PASS_H_

View File

@ -451,16 +451,6 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
RegisterPass(CreateWorkaround1209Pass());
} else if (pass_name == "replace-invalid-opcode") {
RegisterPass(CreateReplaceInvalidOpcodePass());
} else if (pass_name == "inst-bindless-check" ||
pass_name == "inst-desc-idx-check" ||
pass_name == "inst-buff-oob-check") {
// preserve legacy names
RegisterPass(CreateInstBindlessCheckPass(23));
RegisterPass(CreateSimplificationPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateBlockMergePass());
} else if (pass_name == "inst-buff-addr-check") {
RegisterPass(CreateInstBuffAddrCheckPass(23));
} else if (pass_name == "convert-relaxed-to-half") {
RegisterPass(CreateConvertRelaxedToHalfPass());
} else if (pass_name == "relax-float-ops") {
@ -1023,22 +1013,12 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
MakeUnique<opt::UpgradeMemoryModel>());
}
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t shader_id) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InstBindlessCheckPass>(shader_id));
}
Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
uint32_t shader_id) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InstDebugPrintfPass>(desc_set, shader_id));
}
Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t shader_id) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InstBuffAddrCheckPass>(shader_id));
}
Optimizer::PassToken CreateConvertRelaxedToHalfPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::ConvertToHalfPass>());

View File

@ -48,8 +48,6 @@
#include "source/opt/if_conversion.h"
#include "source/opt/inline_exhaustive_pass.h"
#include "source/opt/inline_opaque_pass.h"
#include "source/opt/inst_bindless_check_pass.h"
#include "source/opt/inst_buff_addr_check_pass.h"
#include "source/opt/inst_debug_printf_pass.h"
#include "source/opt/interface_var_sroa.h"
#include "source/opt/interp_fixup_pass.h"

View File

@ -60,8 +60,6 @@ add_spvtools_unittest(TARGET opt
inline_opaque_test.cpp
inline_test.cpp
insert_extract_elim_test.cpp
inst_bindless_check_test.cpp
inst_buff_addr_check_test.cpp
inst_debug_printf_test.cpp
instruction_list_test.cpp
instruction_test.cpp

File diff suppressed because it is too large Load Diff

View File

@ -1,772 +0,0 @@
// Copyright (c) 2019-2022 Valve Corporation
// Copyright (c) 2019-2022 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Bindless Check Instrumentation Tests.
// Tests ending with V2 use version 2 record format.
#include <string>
#include <vector>
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"
namespace spvtools {
namespace opt {
namespace {
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 kImportStub = R"(
;CHECK: %)" + kFuncName + R"( = OpFunction %bool None {{%\w+}}
;CHECK: OpFunctionEnd
)";
// clang-format on
using InstBuffAddrTest = PassTest<::testing::Test>;
TEST_F(InstBuffAddrTest, InstPhysicalStorageBufferStore) {
// #version 450
// #extension GL_EXT_buffer_reference : enable
//
// layout(buffer_reference, buffer_reference_align = 16) buffer bufStruct;
//
// layout(set = 0, binding = 0) uniform ufoo {
// bufStruct data;
// uint offset;
// } u_info;
//
// layout(buffer_reference, std140) buffer bufStruct {
// layout(offset = 0) int a[2];
// layout(offset = 32) int b;
// };
//
// void main() {
// u_info.data.b = 0xca7;
// }
const std::string defs = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
;CHECK: OpCapability Int64
OpExtension "SPV_EXT_physical_storage_buffer"
;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
OpExecutionMode %main LocalSize 1 1 1
OpSource GLSL 450
OpSourceExtension "GL_EXT_buffer_reference"
OpName %main "main"
OpName %ufoo "ufoo"
OpMemberName %ufoo 0 "data"
OpMemberName %ufoo 1 "offset"
OpName %bufStruct "bufStruct"
OpMemberName %bufStruct 0 "a"
OpMemberName %bufStruct 1 "b"
OpName %u_info "u_info"
)";
// clang-format off
const std::string decorates = R"(
OpMemberDecorate %ufoo 0 Offset 0
OpMemberDecorate %ufoo 1 Offset 8
OpDecorate %ufoo Block
OpDecorate %_arr_int_uint_2 ArrayStride 16
OpMemberDecorate %bufStruct 0 Offset 0
OpMemberDecorate %bufStruct 1 Offset 32
OpDecorate %bufStruct Block
OpDecorate %u_info DescriptorSet 0
OpDecorate %u_info Binding 0
)" + kImportDeco + R"(
;CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
)";
const std::string globals = R"(
%void = OpTypeVoid
%3 = OpTypeFunction %void
OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_bufStruct PhysicalStorageBuffer
%uint = OpTypeInt 32 0
%ufoo = OpTypeStruct %_ptr_PhysicalStorageBuffer_bufStruct %uint
%int = OpTypeInt 32 1
%uint_2 = OpConstant %uint 2
%_arr_int_uint_2 = OpTypeArray %int %uint_2
%bufStruct = OpTypeStruct %_arr_int_uint_2 %int
%_ptr_PhysicalStorageBuffer_bufStruct = OpTypePointer PhysicalStorageBuffer %bufStruct
%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo
%u_info = OpVariable %_ptr_Uniform_ufoo Uniform
%int_0 = OpConstant %int 0
%_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct = OpTypePointer Uniform %_ptr_PhysicalStorageBuffer_bufStruct
%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: %v3uint = OpTypeVector %uint 3
;CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
;CHECK: %gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
)";
// clang-format off
const std::string main_func = R"(
%main = OpFunction %void None %3
%5 = OpLabel
%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+}} = 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: OpBranch {{%\w+}}
;CHECK: {{%\w+}} = OpLabel
OpReturn
OpFunctionEnd
)";
// SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(
defs + decorates + globals + kImportStub + main_func, true, 23u);
}
TEST_F(InstBuffAddrTest, InstPhysicalStorageBufferLoadAndStore) {
// #version 450
// #extension GL_EXT_buffer_reference : enable
// // forward reference
// layout(buffer_reference) buffer blockType;
// layout(buffer_reference, std430, buffer_reference_align = 16) buffer
// blockType {
// int x;
// blockType next;
// };
// layout(std430) buffer rootBlock {
// blockType root;
// } r;
// void main()
// {
// blockType b = r.root;
// b = b.next;
// b.x = 531;
// }
const std::string defs = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
;CHECK: OpCapability Int64
OpExtension "SPV_EXT_physical_storage_buffer"
OpExtension "SPV_KHR_storage_buffer_storage_class"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpSource GLSL 450
OpSourceExtension "GL_EXT_buffer_reference"
OpName %main "main"
;CHECK: OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
OpName %blockType "blockType"
OpMemberName %blockType 0 "x"
OpMemberName %blockType 1 "next"
OpName %rootBlock "rootBlock"
OpMemberName %rootBlock 0 "root"
OpName %r "r"
)";
// clang-format off
const std::string decorates = R"(
OpMemberDecorate %blockType 0 Offset 0
OpMemberDecorate %blockType 1 Offset 8
OpDecorate %blockType Block
OpMemberDecorate %rootBlock 0 Offset 0
OpDecorate %rootBlock Block
OpDecorate %r DescriptorSet 0
OpDecorate %r Binding 0
)" + kImportDeco + R"(
;CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
)";
const std::string globals = R"(
%void = OpTypeVoid
%3 = OpTypeFunction %void
OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_blockType PhysicalStorageBuffer
%int = OpTypeInt 32 1
%blockType = OpTypeStruct %int %_ptr_PhysicalStorageBuffer_blockType
%_ptr_PhysicalStorageBuffer_blockType = OpTypePointer PhysicalStorageBuffer %blockType
%rootBlock = OpTypeStruct %_ptr_PhysicalStorageBuffer_blockType
%_ptr_StorageBuffer_rootBlock = OpTypePointer StorageBuffer %rootBlock
%r = OpVariable %_ptr_StorageBuffer_rootBlock StorageBuffer
%int_0 = OpConstant %int 0
%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_blockType = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_blockType
%int_1 = OpConstant %int 1
%_ptr_PhysicalStorageBuffer__ptr_PhysicalStorageBuffer_blockType = OpTypePointer PhysicalStorageBuffer %_ptr_PhysicalStorageBuffer_blockType
%int_531 = OpConstant %int 531
%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
)";
const std::string main_func = R"(
%main = OpFunction %void None %3
%5 = OpLabel
%16 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_blockType %r %int_0
%17 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %16
%21 = OpAccessChain %_ptr_PhysicalStorageBuffer__ptr_PhysicalStorageBuffer_blockType %17 %int_1
%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: {{%\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
)";
// clang-format on
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(
defs + decorates + globals + kImportStub + main_func, true, 23u);
}
TEST_F(InstBuffAddrTest, StructLoad) {
// #version 450
// #extension GL_EXT_buffer_reference : enable
// #extension GL_ARB_gpu_shader_int64 : enable
// struct Test {
// float a;
// };
//
// layout(buffer_reference, std430, buffer_reference_align = 16) buffer
// TestBuffer { Test test; };
//
// Test GetTest(uint64_t ptr) {
// return TestBuffer(ptr).test;
// }
//
// void main() {
// GetTest(0xe0000000);
// }
const std::string defs =
R"(
OpCapability Shader
OpCapability Int64
OpCapability PhysicalStorageBufferAddresses
;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" %gl_FragCoord
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpSourceExtension "GL_ARB_gpu_shader_int64"
OpSourceExtension "GL_EXT_buffer_reference"
OpName %main "main"
OpName %Test "Test"
OpMemberName %Test 0 "a"
OpName %Test_0 "Test"
OpMemberName %Test_0 0 "a"
OpName %TestBuffer "TestBuffer"
OpMemberName %TestBuffer 0 "test"
)";
// clang-format off
const std::string decorates = R"(
OpMemberDecorate %Test_0 0 Offset 0
OpMemberDecorate %TestBuffer 0 Offset 0
OpDecorate %TestBuffer Block
)" + kImportDeco + R"(
;CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
)";
const std::string globals = R"(
%void = OpTypeVoid
%3 = OpTypeFunction %void
%ulong = OpTypeInt 64 0
%float = OpTypeFloat 32
%Test = OpTypeStruct %float
OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_TestBuffer PhysicalStorageBuffer
%Test_0 = OpTypeStruct %float
%TestBuffer = OpTypeStruct %Test_0
%_ptr_PhysicalStorageBuffer_TestBuffer = OpTypePointer PhysicalStorageBuffer %TestBuffer
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_ptr_PhysicalStorageBuffer_Test_0 = OpTypePointer PhysicalStorageBuffer %Test_0
%ulong_18446744073172680704 = OpConstant %ulong 18446744073172680704
;CHECK: {{%\w+}} = OpConstantNull %Test_0
)";
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+}} = 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]]
OpReturn
OpFunctionEnd
)";
// clang-format on
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(
defs + decorates + globals + kImportStub + main_func, true);
}
TEST_F(InstBuffAddrTest, PaddedStructLoad) {
// #version 450
// #extension GL_EXT_buffer_reference : enable
// #extension GL_ARB_gpu_shader_int64 : enable
// struct Test {
// uvec3 pad_1; // Offset 0 Size 12
// double pad_2; // Offset 16 Size 8 (alignment requirement)
// float a; // Offset 24 Size 4
// }; // Total Size 28
//
// layout(buffer_reference, std430, buffer_reference_align = 16) buffer
// TestBuffer { Test test; };
//
// Test GetTest(uint64_t ptr) {
// return TestBuffer(ptr).test;
// }
//
// void main() {
// GetTest(0xe0000000);
// }
const std::string defs =
R"(
OpCapability Shader
OpCapability Float64
OpCapability Int64
OpCapability PhysicalStorageBufferAddresses
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Vertex %main "main"
OpSource GLSL 450
OpSourceExtension "GL_ARB_gpu_shader_int64"
OpSourceExtension "GL_EXT_buffer_reference"
OpName %main "main"
OpName %Test "Test"
OpMemberName %Test 0 "pad_1"
OpMemberName %Test 1 "pad_2"
OpMemberName %Test 2 "a"
OpName %GetTest_u641_ "GetTest(u641;"
OpName %ptr "ptr"
OpName %Test_0 "Test"
OpMemberName %Test_0 0 "pad_1"
OpMemberName %Test_0 1 "pad_2"
OpMemberName %Test_0 2 "a"
OpName %TestBuffer "TestBuffer"
OpMemberName %TestBuffer 0 "test"
OpName %param "param"
)";
// clang-format off
const std::string decorates = R"(
OpDecorate %TestBuffer Block
OpMemberDecorate %Test_0 0 Offset 0
OpMemberDecorate %Test_0 1 Offset 16
OpMemberDecorate %Test_0 2 Offset 24
OpMemberDecorate %TestBuffer 0 Offset 0
)" + kImportDeco + R"(
;CHECK: OpDecorate %gl_VertexIndex BuiltIn VertexIndex
;CHECK: OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
)";
const std::string globals = R"(
%void = OpTypeVoid
%3 = OpTypeFunction %void
%ulong = OpTypeInt 64 0
%_ptr_Function_ulong = OpTypePointer Function %ulong
%uint = OpTypeInt 32 0
%v3uint = OpTypeVector %uint 3
%double = OpTypeFloat 64
%float = OpTypeFloat 32
%Test = OpTypeStruct %v3uint %double %float
%13 = OpTypeFunction %Test %_ptr_Function_ulong
OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_TestBuffer PhysicalStorageBuffer
%Test_0 = OpTypeStruct %v3uint %double %float
%TestBuffer = OpTypeStruct %Test_0
%_ptr_PhysicalStorageBuffer_TestBuffer = OpTypePointer PhysicalStorageBuffer %TestBuffer
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_ptr_PhysicalStorageBuffer_Test_0 = OpTypePointer PhysicalStorageBuffer %Test_0
%_ptr_Function_Test = OpTypePointer Function %Test
%ulong_18446744073172680704 = OpConstant %ulong 18446744073172680704
;CHECK: {{%\w+}} = OpConstantNull %Test_0
)";
const std::string main_func = R"(
%main = OpFunction %void None %3
%5 = OpLabel
%param = OpVariable %_ptr_Function_ulong Function
OpStore %param %ulong_18446744073172680704
%35 = OpFunctionCall %Test %GetTest_u641_ %param
OpReturn
OpFunctionEnd
%GetTest_u641_ = OpFunction %Test None %13
%ptr = OpFunctionParameter %_ptr_Function_ulong
%16 = OpLabel
%28 = OpVariable %_ptr_Function_Test Function
%17 = OpLoad %ulong %ptr
%21 = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_TestBuffer %17
%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+}} = 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
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(
defs + decorates + globals + kImportStub + main_func, true);
}
TEST_F(InstBuffAddrTest, DeviceBufferAddressOOB) {
// #version 450
// #extension GL_EXT_buffer_reference : enable
// layout(buffer_reference, buffer_reference_align = 16) buffer bufStruct;
// layout(set = 0, binding = 0) uniform ufoo {
// bufStruct data;
// int nWrites;
// } u_info;
// layout(buffer_reference, std140) buffer bufStruct {
// int a[4];
// };
// void main() {
// for (int i=0; i < u_info.nWrites; ++i) {
// u_info.data.a[i] = 0xdeadca71;
// }
// }
// clang-format off
const std::string text = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Vertex %main "main" %u_info
;CHECK: OpEntryPoint Vertex %main "main" %u_info %gl_VertexIndex %gl_InstanceIndex
OpSource GLSL 450
OpSourceExtension "GL_EXT_buffer_reference"
OpName %main "main"
OpName %i "i"
OpName %ufoo "ufoo"
OpMemberName %ufoo 0 "data"
OpMemberName %ufoo 1 "nWrites"
OpName %bufStruct "bufStruct"
OpMemberName %bufStruct 0 "a"
OpName %u_info "u_info"
OpMemberDecorate %ufoo 0 Offset 0
OpMemberDecorate %ufoo 1 Offset 8
OpDecorate %ufoo Block
OpDecorate %_arr_int_uint_4 ArrayStride 16
OpMemberDecorate %bufStruct 0 Offset 0
OpDecorate %bufStruct Block
OpDecorate %u_info DescriptorSet 0
OpDecorate %u_info Binding 0
)" + kImportDeco + R"(
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
%int_0 = OpConstant %int 0
OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_bufStruct PhysicalStorageBuffer
%ufoo = OpTypeStruct %_ptr_PhysicalStorageBuffer_bufStruct %int
%uint = OpTypeInt 32 0
%uint_4 = OpConstant %uint 4
%_arr_int_uint_4 = OpTypeArray %int %uint_4
%bufStruct = OpTypeStruct %_arr_int_uint_4
%_ptr_PhysicalStorageBuffer_bufStruct = OpTypePointer PhysicalStorageBuffer %bufStruct
%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo
%u_info = OpVariable %_ptr_Uniform_ufoo Uniform
%int_1 = OpConstant %int 1
%_ptr_Uniform_int = OpTypePointer Uniform %int
%bool = OpTypeBool
%_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct = OpTypePointer Uniform %_ptr_PhysicalStorageBuffer_bufStruct
%int_n559035791 = OpConstant %int -559035791
%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
)" + kImportStub + R"(
%main = OpFunction %void None %3
%5 = OpLabel
%i = OpVariable %_ptr_Function_int Function
OpStore %i %int_0
OpBranch %10
%10 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%15 = OpLoad %int %i
%26 = OpAccessChain %_ptr_Uniform_int %u_info %int_1
%27 = OpLoad %int %26
%29 = OpSLessThan %bool %15 %27
OpBranchConditional %29 %11 %12
%11 = OpLabel
%31 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
%32 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %31
%33 = OpLoad %int %i
%36 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %32 %int_0 %33
OpStore %36 %int_n559035791 Aligned 16
;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 %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
%13 = OpLabel
%37 = OpLoad %int %i
%38 = OpIAdd %int %37 %int_1
OpStore %i %38
OpBranch %10
%12 = OpLabel
OpReturn
OpFunctionEnd)";
// clang-format on
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<InstBuffAddrCheckPass>(text, true, 23);
}
TEST_F(InstBuffAddrTest, UVec3ScalarAddressOOB) {
// clang-format off
// #version 450
// #extension GL_EXT_buffer_reference : enable
// #extension GL_EXT_scalar_block_layout : enable
// layout(buffer_reference, std430, scalar) readonly buffer IndexBuffer
// {
// uvec3 indices[];
// };
// layout(set = 0, binding = 0) uniform ufoo {
// IndexBuffer data;
// int nReads;
// } u_info;
// void main() {
// uvec3 readvec;
// for (int i=0; i < u_info.nReads; ++i) {
// readvec = u_info.data.indices[i];
// }
// }
const std::string text = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Vertex %main "main" %u_info
OpSource GLSL 450
OpSourceExtension "GL_EXT_buffer_reference"
OpSourceExtension "GL_EXT_scalar_block_layout"
OpName %main "main"
OpName %i "i"
OpName %ufoo "ufoo"
OpMemberName %ufoo 0 "data"
OpMemberName %ufoo 1 "nReads"
OpName %IndexBuffer "IndexBuffer"
OpMemberName %IndexBuffer 0 "indices"
OpName %u_info "u_info"
OpName %readvec "readvec"
OpMemberDecorate %ufoo 0 Offset 0
OpMemberDecorate %ufoo 1 Offset 8
OpDecorate %ufoo Block
OpDecorate %_runtimearr_v3uint ArrayStride 12
OpMemberDecorate %IndexBuffer 0 NonWritable
OpMemberDecorate %IndexBuffer 0 Offset 0
OpDecorate %IndexBuffer Block
OpDecorate %u_info DescriptorSet 0
OpDecorate %u_info Binding 0
)" + kImportDeco + R"(
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
%int_0 = OpConstant %int 0
OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_IndexBuffer PhysicalStorageBuffer
%ufoo = OpTypeStruct %_ptr_PhysicalStorageBuffer_IndexBuffer %int
%uint = OpTypeInt 32 0
%v3uint = OpTypeVector %uint 3
%_runtimearr_v3uint = OpTypeRuntimeArray %v3uint
%IndexBuffer = OpTypeStruct %_runtimearr_v3uint
%_ptr_PhysicalStorageBuffer_IndexBuffer = OpTypePointer PhysicalStorageBuffer %IndexBuffer
%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo
%u_info = OpVariable %_ptr_Uniform_ufoo Uniform
%int_1 = OpConstant %int 1
%_ptr_Uniform_int = OpTypePointer Uniform %int
%bool = OpTypeBool
)" + kImportStub + R"(
%_ptr_Function_v3uint = OpTypePointer Function %v3uint
%_ptr_Uniform__ptr_PhysicalStorageBuffer_IndexBuffer = OpTypePointer Uniform %_ptr_PhysicalStorageBuffer_IndexBuffer
%_ptr_PhysicalStorageBuffer_v3uint = OpTypePointer PhysicalStorageBuffer %v3uint
%main = OpFunction %void None %3
%5 = OpLabel
%i = OpVariable %_ptr_Function_int Function
%readvec = OpVariable %_ptr_Function_v3uint Function
OpStore %i %int_0
OpBranch %10
%10 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%15 = OpLoad %int %i
%26 = OpAccessChain %_ptr_Uniform_int %u_info %int_1
%27 = OpLoad %int %26
%29 = OpSLessThan %bool %15 %27
OpBranchConditional %29 %11 %12
%11 = OpLabel
%33 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_IndexBuffer %u_info %int_0
%34 = OpLoad %_ptr_PhysicalStorageBuffer_IndexBuffer %33
%35 = OpLoad %int %i
%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: {{%\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
%40 = OpIAdd %int %39 %int_1
OpStore %i %40
OpBranch %10
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
// clang-format on
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ValidatorOptions()->scalar_block_layout = true;
SinglePassRunAndMatch<InstBuffAddrCheckPass>(text, true, 23);
}
} // namespace
} // namespace opt
} // namespace spvtools