mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-24 16:51:06 +00:00
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:
parent
bfc3a15683
commit
9241a58a80
@ -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 \
|
||||
|
4
BUILD.gn
4
BUILD.gn
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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_
|
@ -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
|
@ -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_
|
@ -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>());
|
||||
|
@ -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"
|
||||
|
@ -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
@ -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
|
Loading…
Reference in New Issue
Block a user