mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-22 11:40:05 +00:00
Add base and core bindless validation instrumentation classes (#2014)
* Add base and core bindless validation instrumentation classes * Fix formatting. * Few more formatting fixes * Fix build failure * More build fixes * Need to call non-const functions in order. Specifically, these are functions which call TakeNextId(). These need to be called in a specific order to guarantee that tests which do exact compares will work across all platforms. c++ pretty much does not guarantee order of evaluation of operands, so any such functions need to be called separately in individual statements to guarantee order. * More ordering. * And more ordering. * And more formatting. * Attempt to fix NDK build * Another attempt to address NDK build problem. * One more attempt at NDK build failure * Add instrument.hpp to BUILD.gn * Some name improvement in instrument.hpp * Change all types in instrument.hpp to int. * Improve documentation in instrument.hpp * Format fixes * Comment clean up in instrument.hpp * imageInst -> image_inst * Fix GetLabel() issue.
This commit is contained in:
parent
6721478ef1
commit
1e9fc1aac1
@ -102,8 +102,10 @@ 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/instruction.cpp \
|
||||
source/opt/instruction_list.cpp \
|
||||
source/opt/instrument_pass.cpp \
|
||||
source/opt/ir_context.cpp \
|
||||
source/opt/ir_loader.cpp \
|
||||
source/opt/licm_pass.cpp \
|
||||
|
5
BUILD.gn
5
BUILD.gn
@ -297,6 +297,7 @@ source_set("spvtools_headers") {
|
||||
"include/spirv-tools/libspirv.hpp",
|
||||
"include/spirv-tools/linker.hpp",
|
||||
"include/spirv-tools/optimizer.hpp",
|
||||
"include/spirv-tools/instrument.hpp",
|
||||
]
|
||||
|
||||
public_configs = [ ":spvtools_public_config" ]
|
||||
@ -511,10 +512,14 @@ 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/instruction.cpp",
|
||||
"source/opt/instruction.h",
|
||||
"source/opt/instruction_list.cpp",
|
||||
"source/opt/instruction_list.h",
|
||||
"source/opt/instrument_pass.cpp",
|
||||
"source/opt/instrument_pass.h",
|
||||
"source/opt/ir_builder.h",
|
||||
"source/opt/ir_context.cpp",
|
||||
"source/opt/ir_context.h",
|
||||
|
@ -239,6 +239,7 @@ if(ENABLE_SPIRV_TOOLS_INSTALL)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/optimizer.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/linker.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/instrument.hpp
|
||||
DESTINATION
|
||||
${CMAKE_INSTALL_INCLUDEDIR}/spirv-tools/)
|
||||
endif(ENABLE_SPIRV_TOOLS_INSTALL)
|
||||
|
135
include/spirv-tools/instrument.hpp
Normal file
135
include/spirv-tools/instrument.hpp
Normal file
@ -0,0 +1,135 @@
|
||||
// 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 INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
|
||||
#define INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
|
||||
|
||||
// Shader Instrumentation Interface
|
||||
//
|
||||
// This file provides an external interface for applications that wish to
|
||||
// communicate with shaders instrumented by passes created by:
|
||||
//
|
||||
// CreateInstBindlessCheckPass
|
||||
//
|
||||
// More detailed documentation of this routine can be found in optimizer.hpp
|
||||
|
||||
namespace spvtools {
|
||||
|
||||
// Stream Output Buffer Offsets
|
||||
//
|
||||
// The following values provide 32-bit word offsets into the output buffer
|
||||
// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
|
||||
// by InstBindlessCheckPass.
|
||||
//
|
||||
// The first word 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
|
||||
// initialized to zero
|
||||
static const int kDebugOutputSizeOffset = 0;
|
||||
|
||||
// The second word of the output buffer is the start of the stream of records
|
||||
// written by the instrumented shaders. Each record represents a validation
|
||||
// error. The format of the records is documented below.
|
||||
static const int kDebugOutputDataOffset = 1;
|
||||
|
||||
// Common Stream Record Offsets
|
||||
//
|
||||
// The following are offsets to fields which are common to all records written
|
||||
// to the output stream.
|
||||
//
|
||||
// Each record first contains the size of the record in 32-bit words, including
|
||||
// the size word.
|
||||
static const int kInstCommonOutSize = 0;
|
||||
|
||||
// This is the shader id passed by the layer when the instrumentation pass is
|
||||
// created.
|
||||
static const int kInstCommonOutShaderId = 1;
|
||||
|
||||
// This is the ordinal position of the instruction within the SPIR-V shader
|
||||
// which generated the validation error.
|
||||
static const int kInstCommonOutInstructionIdx = 2;
|
||||
|
||||
// This is the stage which generated the validation error. This word is used
|
||||
// to determine the contents of the next two words in the record.
|
||||
// 0:Vert, 1:TessCtrl, 2:TessEval, 3:Geom, 4:Frag, 5:Compute
|
||||
static const int kInstCommonOutStageIdx = 3;
|
||||
static const int kInstCommonOutCnt = 4;
|
||||
|
||||
// Stage-specific Stream Record Offsets
|
||||
//
|
||||
// Each stage will contain different values in the next two words of the record
|
||||
// used to identify which instantiation of the shader generated the validation
|
||||
// error.
|
||||
//
|
||||
// Vertex Shader Output Record Offsets
|
||||
static const int kInstVertOutVertexId = kInstCommonOutCnt;
|
||||
static const int kInstVertOutInstanceId = kInstCommonOutCnt + 1;
|
||||
|
||||
// Frag Shader Output Record Offsets
|
||||
static const int kInstFragOutFragCoordX = kInstCommonOutCnt;
|
||||
static const int kInstFragOutFragCoordY = kInstCommonOutCnt + 1;
|
||||
|
||||
// Compute Shader Output Record Offsets
|
||||
static const int kInstCompOutGlobalInvocationId = kInstCommonOutCnt;
|
||||
static const int kInstCompOutUnused = kInstCommonOutCnt + 1;
|
||||
|
||||
// Tessellation Shader Output Record Offsets
|
||||
static const int kInstTessOutInvocationId = kInstCommonOutCnt;
|
||||
static const int kInstTessOutUnused = kInstCommonOutCnt + 1;
|
||||
|
||||
// Geometry Shader Output Record Offsets
|
||||
static const int kInstGeomOutPrimitiveId = kInstCommonOutCnt;
|
||||
static const int kInstGeomOutInvocationId = kInstCommonOutCnt + 1;
|
||||
|
||||
// Size of Common and Stage-specific Members
|
||||
static const int kInstStageOutCnt = kInstCommonOutCnt + 2;
|
||||
|
||||
// Validation Error Code
|
||||
//
|
||||
// This identifies the validation error. It also helps to identify
|
||||
// how many words follow in the record and their meaning.
|
||||
static const int kInstValidationOutError = kInstStageOutCnt;
|
||||
|
||||
// Validation-specific Output Record Offsets
|
||||
//
|
||||
// Each different validation will generate a potentially different
|
||||
// number of words at the end of the record giving more specifics
|
||||
// about the validation error.
|
||||
//
|
||||
// A bindless bounds error will output the index and the bound.
|
||||
static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1;
|
||||
static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2;
|
||||
static const int kInstBindlessOutCnt = kInstStageOutCnt + 3;
|
||||
|
||||
// Maximum Output Record Member Count
|
||||
static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
|
||||
|
||||
// Validation Error Codes
|
||||
//
|
||||
// These are the possible validation error codes.
|
||||
static const int kInstErrorBindlessBounds = 0;
|
||||
|
||||
// Debug Buffer Bindings
|
||||
//
|
||||
// These are the bindings for the different buffers which are
|
||||
// read or written by the instrumentation passes.
|
||||
//
|
||||
// This is the output buffer written by InstBindlessCheckPass.
|
||||
static const int kDebugOutputBindingStream = 0;
|
||||
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
|
@ -657,6 +657,32 @@ 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. 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.
|
||||
//
|
||||
// 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 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.
|
||||
//
|
||||
// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
|
||||
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
|
||||
uint32_t shader_id);
|
||||
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
|
||||
|
@ -46,8 +46,10 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
inline_exhaustive_pass.h
|
||||
inline_opaque_pass.h
|
||||
inline_pass.h
|
||||
inst_bindless_check_pass.h
|
||||
instruction.h
|
||||
instruction_list.h
|
||||
instrument_pass.h
|
||||
ir_builder.h
|
||||
ir_context.h
|
||||
ir_loader.h
|
||||
@ -134,8 +136,10 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
inline_exhaustive_pass.cpp
|
||||
inline_opaque_pass.cpp
|
||||
inline_pass.cpp
|
||||
inst_bindless_check_pass.cpp
|
||||
instruction.cpp
|
||||
instruction_list.cpp
|
||||
instrument_pass.cpp
|
||||
ir_context.cpp
|
||||
ir_loader.cpp
|
||||
licm_pass.cpp
|
||||
|
@ -71,6 +71,9 @@ class BasicBlock {
|
||||
// Appends all of block's instructions (except label) to this block
|
||||
inline void AddInstructions(BasicBlock* bp);
|
||||
|
||||
// The pointer to the label starting this basic block.
|
||||
std::unique_ptr<Instruction>& GetLabel() { return label_; }
|
||||
|
||||
// The label starting this basic block.
|
||||
Instruction* GetLabelInst() { return label_.get(); }
|
||||
const Instruction* GetLabelInst() const { return label_.get(); }
|
||||
|
@ -261,6 +261,7 @@ void DecorationManager::AnalyzeDecorations() {
|
||||
AddDecoration(&inst);
|
||||
}
|
||||
}
|
||||
|
||||
void DecorationManager::AddDecoration(Instruction* inst) {
|
||||
switch (inst->opcode()) {
|
||||
case SpvOpDecorate:
|
||||
@ -289,6 +290,43 @@ void DecorationManager::AddDecoration(Instruction* inst) {
|
||||
}
|
||||
}
|
||||
|
||||
void DecorationManager::AddDecoration(SpvOp opcode,
|
||||
std::vector<Operand> opnds) {
|
||||
IRContext* ctx = module_->context();
|
||||
std::unique_ptr<Instruction> newDecoOp(
|
||||
new Instruction(ctx, opcode, 0, 0, opnds));
|
||||
ctx->AddAnnotationInst(std::move(newDecoOp));
|
||||
}
|
||||
|
||||
void DecorationManager::AddDecoration(uint32_t inst_id, uint32_t decoration) {
|
||||
AddDecoration(
|
||||
SpvOpDecorate,
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}}});
|
||||
}
|
||||
|
||||
void DecorationManager::AddDecorationVal(uint32_t inst_id, uint32_t decoration,
|
||||
uint32_t decoration_value) {
|
||||
AddDecoration(
|
||||
SpvOpDecorate,
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||
{decoration_value}}});
|
||||
}
|
||||
|
||||
void DecorationManager::AddMemberDecoration(uint32_t inst_id, uint32_t member,
|
||||
uint32_t decoration,
|
||||
uint32_t decoration_value) {
|
||||
AddDecoration(
|
||||
SpvOpMemberDecorate,
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inst_id}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {member}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||
{decoration_value}}});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> DecorationManager::InternalGetDecorationsFor(
|
||||
uint32_t id, bool include_linkage) {
|
||||
|
@ -111,6 +111,20 @@ class DecorationManager {
|
||||
// Informs the decoration manager of a new decoration that it needs to track.
|
||||
void AddDecoration(Instruction* inst);
|
||||
|
||||
// Add decoration with |opcode| and operands |opnds|.
|
||||
void AddDecoration(SpvOp opcode, const std::vector<Operand> opnds);
|
||||
|
||||
// Add |decoration| of |inst_id| to module.
|
||||
void AddDecoration(uint32_t inst_id, uint32_t decoration);
|
||||
|
||||
// Add |decoration, decoration_value| of |inst_id| to module.
|
||||
void AddDecorationVal(uint32_t inst_id, uint32_t decoration,
|
||||
uint32_t decoration_value);
|
||||
|
||||
// Add |decoration, decoration_value| of |inst_id, member| to module.
|
||||
void AddMemberDecoration(uint32_t member, uint32_t inst_id,
|
||||
uint32_t decoration, uint32_t decoration_value);
|
||||
|
||||
private:
|
||||
// Analyzes the defs and uses in the given |module| and populates data
|
||||
// structures in this class. Does nothing if |module| is nullptr.
|
||||
|
@ -279,6 +279,16 @@ bool operator==(const DefUseManager& lhs, const DefUseManager& rhs) {
|
||||
}
|
||||
|
||||
if (lhs.inst_to_used_ids_ != rhs.inst_to_used_ids_) {
|
||||
for (auto p : lhs.inst_to_used_ids_) {
|
||||
if (rhs.inst_to_used_ids_.count(p.first) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto p : rhs.inst_to_used_ids_) {
|
||||
if (lhs.inst_to_used_ids_.count(p.first) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
263
source/opt/inst_bindless_check_pass.cpp
Normal file
263
source/opt/inst_bindless_check_pass.cpp
Normal file
@ -0,0 +1,263 @@
|
||||
// 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"
|
||||
|
||||
namespace {
|
||||
|
||||
// Input Operand Indices
|
||||
static const int kSpvImageSampleImageIdInIdx = 0;
|
||||
static const int kSpvSampledImageImageIdInIdx = 0;
|
||||
static const int kSpvSampledImageSamplerIdInIdx = 1;
|
||||
static const int kSpvImageSampledImageIdInIdx = 0;
|
||||
static const int kSpvLoadPtrIdInIdx = 0;
|
||||
static const int kSpvAccessChainBaseIdInIdx = 0;
|
||||
static const int kSpvAccessChainIndex0IdInIdx = 1;
|
||||
static const int kSpvTypePointerTypeIdInIdx = 1;
|
||||
static const int kSpvTypeArrayLengthIdInIdx = 1;
|
||||
static const int kSpvConstantValueInIdx = 0;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
void InstBindlessCheckPass::GenBindlessCheckCode(
|
||||
BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
|
||||
uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
// Look for reference through bindless descriptor. If not, return.
|
||||
std::unique_ptr<BasicBlock> new_blk_ptr;
|
||||
uint32_t image_id;
|
||||
switch (ref_inst_itr->opcode()) {
|
||||
case SpvOp::SpvOpImageSampleImplicitLod:
|
||||
case SpvOp::SpvOpImageSampleExplicitLod:
|
||||
case SpvOp::SpvOpImageSampleDrefImplicitLod:
|
||||
case SpvOp::SpvOpImageSampleDrefExplicitLod:
|
||||
case SpvOp::SpvOpImageSampleProjImplicitLod:
|
||||
case SpvOp::SpvOpImageSampleProjExplicitLod:
|
||||
case SpvOp::SpvOpImageSampleProjDrefImplicitLod:
|
||||
case SpvOp::SpvOpImageSampleProjDrefExplicitLod:
|
||||
case SpvOp::SpvOpImageGather:
|
||||
case SpvOp::SpvOpImageDrefGather:
|
||||
case SpvOp::SpvOpImageQueryLod:
|
||||
case SpvOp::SpvOpImageSparseSampleImplicitLod:
|
||||
case SpvOp::SpvOpImageSparseSampleExplicitLod:
|
||||
case SpvOp::SpvOpImageSparseSampleDrefImplicitLod:
|
||||
case SpvOp::SpvOpImageSparseSampleDrefExplicitLod:
|
||||
case SpvOp::SpvOpImageSparseSampleProjImplicitLod:
|
||||
case SpvOp::SpvOpImageSparseSampleProjExplicitLod:
|
||||
case SpvOp::SpvOpImageSparseSampleProjDrefImplicitLod:
|
||||
case SpvOp::SpvOpImageSparseSampleProjDrefExplicitLod:
|
||||
case SpvOp::SpvOpImageSparseGather:
|
||||
case SpvOp::SpvOpImageSparseDrefGather:
|
||||
case SpvOp::SpvOpImageFetch:
|
||||
case SpvOp::SpvOpImageRead:
|
||||
case SpvOp::SpvOpImageQueryFormat:
|
||||
case SpvOp::SpvOpImageQueryOrder:
|
||||
case SpvOp::SpvOpImageQuerySizeLod:
|
||||
case SpvOp::SpvOpImageQuerySize:
|
||||
case SpvOp::SpvOpImageQueryLevels:
|
||||
case SpvOp::SpvOpImageQuerySamples:
|
||||
case SpvOp::SpvOpImageSparseFetch:
|
||||
case SpvOp::SpvOpImageSparseRead:
|
||||
case SpvOp::SpvOpImageWrite:
|
||||
image_id =
|
||||
ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
Instruction* image_inst = get_def_use_mgr()->GetDef(image_id);
|
||||
uint32_t load_id;
|
||||
Instruction* load_inst;
|
||||
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
|
||||
load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
|
||||
load_inst = get_def_use_mgr()->GetDef(load_id);
|
||||
} else if (image_inst->opcode() == SpvOp::SpvOpImage) {
|
||||
load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
|
||||
load_inst = get_def_use_mgr()->GetDef(load_id);
|
||||
} else {
|
||||
load_id = image_id;
|
||||
load_inst = image_inst;
|
||||
image_id = 0;
|
||||
}
|
||||
if (load_inst->opcode() != SpvOp::SpvOpLoad) {
|
||||
// TODO(greg-lunarg): Handle additional possibilities
|
||||
return;
|
||||
}
|
||||
uint32_t ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
|
||||
Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
|
||||
if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
|
||||
if (ptr_inst->NumInOperands() != 2) {
|
||||
assert(false && "unexpected bindless index number");
|
||||
return;
|
||||
}
|
||||
uint32_t index_id =
|
||||
ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
|
||||
ptr_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
|
||||
ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
|
||||
if (ptr_inst->opcode() != SpvOpVariable) {
|
||||
assert(false && "unexpected bindless base");
|
||||
return;
|
||||
}
|
||||
uint32_t var_type_id = ptr_inst->type_id();
|
||||
Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
|
||||
uint32_t ptr_type_id =
|
||||
var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
|
||||
Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
|
||||
// TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
|
||||
// out of debug input buffer.
|
||||
if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
|
||||
// If index and bound both compile-time constants and index < bound,
|
||||
// return without changing
|
||||
uint32_t length_id =
|
||||
ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
|
||||
Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
|
||||
Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
|
||||
if (index_inst->opcode() == SpvOpConstant &&
|
||||
length_inst->opcode() == SpvOpConstant &&
|
||||
index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
|
||||
length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
|
||||
return;
|
||||
// Generate full runtime bounds test code with true branch
|
||||
// being full reference and false branch being debug output and zero
|
||||
// for the referenced value.
|
||||
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
|
||||
InstructionBuilder builder(
|
||||
context(), &*new_blk_ptr,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
|
||||
Instruction* ult_inst =
|
||||
builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
|
||||
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(ult_inst->result_id(), valid_blk_id,
|
||||
invalid_blk_id, merge_blk_id,
|
||||
SpvSelectionControlMaskNone);
|
||||
// Close selection block and gen valid reference block
|
||||
new_blocks->push_back(std::move(new_blk_ptr));
|
||||
new_blk_ptr.reset(new BasicBlock(std::move(valid_label)));
|
||||
builder.SetInsertPoint(&*new_blk_ptr);
|
||||
// Clone descriptor load
|
||||
Instruction* new_load_inst =
|
||||
builder.AddLoad(load_inst->type_id(),
|
||||
load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
|
||||
uint32_t new_load_id = new_load_inst->result_id();
|
||||
get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
|
||||
uint32_t new_image_id = new_load_id;
|
||||
// Clone Image/SampledImage with new load, if needed
|
||||
if (image_id != 0) {
|
||||
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
|
||||
Instruction* new_image_inst = builder.AddBinaryOp(
|
||||
image_inst->type_id(), SpvOpSampledImage, new_load_id,
|
||||
image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
|
||||
new_image_id = new_image_inst->result_id();
|
||||
} else {
|
||||
assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
|
||||
Instruction* new_image_inst =
|
||||
builder.AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
|
||||
new_image_id = new_image_inst->result_id();
|
||||
}
|
||||
get_decoration_mgr()->CloneDecorations(image_id, new_image_id);
|
||||
}
|
||||
// Clone original reference using new image code
|
||||
std::unique_ptr<Instruction> new_ref_inst(ref_inst_itr->Clone(context()));
|
||||
uint32_t ref_result_id = ref_inst_itr->result_id();
|
||||
uint32_t new_ref_id = 0;
|
||||
if (ref_result_id != 0) {
|
||||
new_ref_id = TakeNextId();
|
||||
new_ref_inst->SetResultId(new_ref_id);
|
||||
}
|
||||
new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
|
||||
// Register new reference and add to new block
|
||||
builder.AddInstruction(std::move(new_ref_inst));
|
||||
if (new_ref_id != 0)
|
||||
get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
|
||||
// Close valid block and gen invalid block
|
||||
(void)builder.AddBranch(merge_blk_id);
|
||||
new_blocks->push_back(std::move(new_blk_ptr));
|
||||
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
|
||||
builder.SetInsertPoint(&*new_blk_ptr);
|
||||
uint32_t u_index_id = GenUintCastCode(index_id, &builder);
|
||||
GenDebugStreamWrite(instruction_idx, stage_idx,
|
||||
{error_id, u_index_id, length_id}, &builder);
|
||||
// Remember last invalid block id
|
||||
uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
|
||||
// Gen zero for invalid reference
|
||||
uint32_t ref_type_id = ref_inst_itr->type_id();
|
||||
// Close invalid block and gen merge block
|
||||
(void)builder.AddBranch(merge_blk_id);
|
||||
new_blocks->push_back(std::move(new_blk_ptr));
|
||||
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 and move in remainder of original block.
|
||||
if (new_ref_id != 0) {
|
||||
Instruction* phi_inst = builder.AddPhi(
|
||||
ref_type_id, {new_ref_id, valid_blk_id, builder.GetNullId(ref_type_id),
|
||||
last_invalid_blk_id});
|
||||
context()->ReplaceAllUsesWith(ref_result_id, phi_inst->result_id());
|
||||
}
|
||||
context()->KillInst(&*ref_inst_itr);
|
||||
MovePostludeCode(ref_block_itr, &new_blk_ptr);
|
||||
// Add remainder/merge block to new blocks
|
||||
new_blocks->push_back(std::move(new_blk_ptr));
|
||||
}
|
||||
|
||||
void InstBindlessCheckPass::InitializeInstBindlessCheck() {
|
||||
// Initialize base class
|
||||
InitializeInstrument();
|
||||
// Look for related extensions
|
||||
ext_descriptor_indexing_defined_ = false;
|
||||
for (auto& ei : get_module()->extensions()) {
|
||||
const char* ext_name =
|
||||
reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
|
||||
if (strcmp(ext_name, "SPV_EXT_descriptor_indexing") == 0) {
|
||||
ext_descriptor_indexing_defined_ = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Pass::Status InstBindlessCheckPass::ProcessImpl() {
|
||||
// Perform instrumentation on each entry point function in module
|
||||
InstProcessFunction pfn =
|
||||
[this](BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
uint32_t instruction_idx, uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
return GenBindlessCheckCode(ref_inst_itr, ref_block_itr,
|
||||
instruction_idx, stage_idx, new_blocks);
|
||||
};
|
||||
bool modified = InstProcessEntryPointCallTree(pfn);
|
||||
// This pass does not update inst->blk info
|
||||
context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping);
|
||||
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
Pass::Status InstBindlessCheckPass::Process() {
|
||||
InitializeInstBindlessCheck();
|
||||
return ProcessImpl();
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
93
source/opt/inst_bindless_check_pass.h
Normal file
93
source/opt/inst_bindless_check_pass.h
Normal file
@ -0,0 +1,93 @@
|
||||
// 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:
|
||||
// For test harness only
|
||||
InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
|
||||
// For all other interfaces
|
||||
InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
|
||||
: InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
|
||||
|
||||
~InstBindlessCheckPass() = default;
|
||||
|
||||
// See optimizer.hpp for pass user documentation.
|
||||
Status Process() override;
|
||||
|
||||
const char* name() const override { return "inst-bindless-check-pass"; }
|
||||
|
||||
private:
|
||||
// Initialize state for instrumenting bindless checking
|
||||
void InitializeInstBindlessCheck();
|
||||
|
||||
// This function does bindless checking instrumentation on a single
|
||||
// instruction. It is designed to be passed to
|
||||
// InstrumentPass::InstProcessEntryPointCallTree(), which applies the
|
||||
// function to each instruction in a module and replaces the instruction
|
||||
// if warranted.
|
||||
//
|
||||
// If |ref_inst_itr| is a bindless reference, return in |new_blocks| the
|
||||
// result of instrumenting it with validation code within its block at
|
||||
// |ref_block_itr|. Specifically, generate code to check that the index
|
||||
// into the descriptor array is in-bounds. If the check passes, execute
|
||||
// the remainder of the reference, otherwise write a record to the debug
|
||||
// output buffer stream including |function_idx, instruction_idx, stage_idx|
|
||||
// and replace 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.
|
||||
//
|
||||
// This instrumentation pass utilizes GenDebugStreamWrite() to write its
|
||||
// error records. The validation-specific part of the error record will
|
||||
// have the format:
|
||||
//
|
||||
// Validation Error Code (=kInstErrorBindlessBounds)
|
||||
// Descriptor Index
|
||||
// Descriptor Array Size
|
||||
//
|
||||
// The Descriptor Index is the index which has been determined to be
|
||||
// out-of-bounds.
|
||||
//
|
||||
// The Descriptor Array Size is the size of the descriptor array which was
|
||||
// indexed.
|
||||
void GenBindlessCheckCode(
|
||||
BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
|
||||
uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
Pass::Status ProcessImpl();
|
||||
|
||||
// True if VK_EXT_descriptor_indexing is defined
|
||||
bool ext_descriptor_indexing_defined_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // LIBSPIRV_OPT_INST_BINDLESS_CHECK_PASS_H_
|
710
source/opt/instrument_pass.cpp
Normal file
710
source/opt/instrument_pass.cpp
Normal file
@ -0,0 +1,710 @@
|
||||
// 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 "instrument_pass.h"
|
||||
|
||||
#include "source/cfa.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Common Parameter Positions
|
||||
static const int kInstCommonParamInstIdx = 0;
|
||||
static const int kInstCommonParamCnt = 1;
|
||||
|
||||
// Indices of operands in SPIR-V instructions
|
||||
static const int kEntryPointExecutionModelInIdx = 0;
|
||||
static const int kEntryPointFunctionIdInIdx = 1;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
void InstrumentPass::MovePreludeCode(
|
||||
BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
std::unique_ptr<BasicBlock>* new_blk_ptr) {
|
||||
same_block_pre_.clear();
|
||||
same_block_post_.clear();
|
||||
// Initialize new block. Reuse label from original block.
|
||||
new_blk_ptr->reset(new BasicBlock(std::move(ref_block_itr->GetLabel())));
|
||||
// Move contents of original ref block up to ref instruction.
|
||||
for (auto cii = ref_block_itr->begin(); cii != ref_inst_itr;
|
||||
cii = ref_block_itr->begin()) {
|
||||
Instruction* inst = &*cii;
|
||||
inst->RemoveFromList();
|
||||
std::unique_ptr<Instruction> mv_ptr(inst);
|
||||
// Remember same-block ops for possible regeneration.
|
||||
if (IsSameBlockOp(&*mv_ptr)) {
|
||||
auto* sb_inst_ptr = mv_ptr.get();
|
||||
same_block_pre_[mv_ptr->result_id()] = sb_inst_ptr;
|
||||
}
|
||||
(*new_blk_ptr)->AddInstruction(std::move(mv_ptr));
|
||||
}
|
||||
}
|
||||
|
||||
void InstrumentPass::MovePostludeCode(
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
std::unique_ptr<BasicBlock>* new_blk_ptr) {
|
||||
// new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id())));
|
||||
// Move contents of original ref block.
|
||||
for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
|
||||
cii = ref_block_itr->begin()) {
|
||||
Instruction* inst = &*cii;
|
||||
inst->RemoveFromList();
|
||||
std::unique_ptr<Instruction> mv_inst(inst);
|
||||
// Regenerate any same-block instruction that has not been seen in the
|
||||
// current block.
|
||||
if (same_block_pre_.size() > 0) {
|
||||
CloneSameBlockOps(&mv_inst, &same_block_post_, &same_block_pre_,
|
||||
new_blk_ptr);
|
||||
// Remember same-block ops in this block.
|
||||
if (IsSameBlockOp(&*mv_inst)) {
|
||||
const uint32_t rid = mv_inst->result_id();
|
||||
same_block_post_[rid] = rid;
|
||||
}
|
||||
}
|
||||
(*new_blk_ptr)->AddInstruction(std::move(mv_inst));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> InstrumentPass::NewLabel(uint32_t label_id) {
|
||||
std::unique_ptr<Instruction> newLabel(
|
||||
new Instruction(context(), SpvOpLabel, 0, label_id, {}));
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*newLabel);
|
||||
return newLabel;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GenUintCastCode(uint32_t val_id,
|
||||
InstructionBuilder* builder) {
|
||||
// Cast value to 32-bit unsigned if necessary
|
||||
if (get_def_use_mgr()->GetDef(val_id)->type_id() == GetUintId())
|
||||
return val_id;
|
||||
return builder->AddUnaryOp(GetUintId(), SpvOpBitcast, val_id)->result_id();
|
||||
}
|
||||
|
||||
void InstrumentPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
|
||||
uint32_t field_offset,
|
||||
uint32_t field_value_id,
|
||||
InstructionBuilder* builder) {
|
||||
// Cast value to 32-bit unsigned if necessary
|
||||
uint32_t val_id = GenUintCastCode(field_value_id, builder);
|
||||
// Store value
|
||||
Instruction* data_idx_inst =
|
||||
builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
|
||||
builder->GetUintConstantId(field_offset));
|
||||
uint32_t buf_id = GetOutputBufferId();
|
||||
uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
|
||||
Instruction* achain_inst =
|
||||
builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
|
||||
builder->GetUintConstantId(kDebugOutputDataOffset),
|
||||
data_idx_inst->result_id());
|
||||
(void)builder->AddBinaryOp(0, SpvOpStore, achain_inst->result_id(), val_id);
|
||||
}
|
||||
|
||||
void InstrumentPass::GenCommonStreamWriteCode(uint32_t record_sz,
|
||||
uint32_t inst_id,
|
||||
uint32_t stage_idx,
|
||||
uint32_t base_offset_id,
|
||||
InstructionBuilder* builder) {
|
||||
// Store record size
|
||||
GenDebugOutputFieldCode(base_offset_id, kInstCommonOutSize,
|
||||
builder->GetUintConstantId(record_sz), builder);
|
||||
// Store Shader Id
|
||||
GenDebugOutputFieldCode(base_offset_id, kInstCommonOutShaderId,
|
||||
builder->GetUintConstantId(shader_id_), builder);
|
||||
// Store Instruction Idx
|
||||
GenDebugOutputFieldCode(base_offset_id, kInstCommonOutInstructionIdx, inst_id,
|
||||
builder);
|
||||
// Store Stage Idx
|
||||
GenDebugOutputFieldCode(base_offset_id, kInstCommonOutStageIdx,
|
||||
builder->GetUintConstantId(stage_idx), builder);
|
||||
}
|
||||
|
||||
void InstrumentPass::GenFragCoordEltDebugOutputCode(
|
||||
uint32_t base_offset_id, uint32_t uint_frag_coord_id, uint32_t element,
|
||||
InstructionBuilder* builder) {
|
||||
Instruction* element_val_inst = builder->AddIdLiteralOp(
|
||||
GetUintId(), SpvOpCompositeExtract, uint_frag_coord_id, element);
|
||||
GenDebugOutputFieldCode(base_offset_id, kInstFragOutFragCoordX + element,
|
||||
element_val_inst->result_id(), builder);
|
||||
}
|
||||
|
||||
void InstrumentPass::GenBuiltinOutputCode(uint32_t builtin_id,
|
||||
uint32_t builtin_off,
|
||||
uint32_t base_offset_id,
|
||||
InstructionBuilder* builder) {
|
||||
// Load and store builtin
|
||||
Instruction* load_inst =
|
||||
builder->AddUnaryOp(GetUintId(), SpvOpLoad, builtin_id);
|
||||
GenDebugOutputFieldCode(base_offset_id, builtin_off, load_inst->result_id(),
|
||||
builder);
|
||||
}
|
||||
|
||||
void InstrumentPass::GenUintNullOutputCode(uint32_t field_off,
|
||||
uint32_t base_offset_id,
|
||||
InstructionBuilder* builder) {
|
||||
GenDebugOutputFieldCode(base_offset_id, field_off,
|
||||
builder->GetNullId(GetUintId()), builder);
|
||||
}
|
||||
|
||||
void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx,
|
||||
uint32_t base_offset_id,
|
||||
InstructionBuilder* builder) {
|
||||
// TODO(greg-lunarg): Add support for all stages
|
||||
switch (stage_idx) {
|
||||
case SpvExecutionModelVertex: {
|
||||
// Load and store VertexId and InstanceId
|
||||
GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexId),
|
||||
kInstVertOutVertexId, base_offset_id, builder);
|
||||
GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceId),
|
||||
kInstVertOutInstanceId, base_offset_id, builder);
|
||||
} break;
|
||||
case SpvExecutionModelGLCompute: {
|
||||
// Load and store GlobalInvocationId. Second word is unused; store zero.
|
||||
GenBuiltinOutputCode(
|
||||
context()->GetBuiltinVarId(SpvBuiltInGlobalInvocationId),
|
||||
kInstCompOutGlobalInvocationId, base_offset_id, builder);
|
||||
GenUintNullOutputCode(kInstCompOutUnused, base_offset_id, builder);
|
||||
} break;
|
||||
case SpvExecutionModelGeometry: {
|
||||
// Load and store PrimitiveId and InvocationId.
|
||||
GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInPrimitiveId),
|
||||
kInstGeomOutPrimitiveId, base_offset_id, builder);
|
||||
GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInvocationId),
|
||||
kInstGeomOutInvocationId, base_offset_id, builder);
|
||||
} break;
|
||||
case SpvExecutionModelTessellationControl:
|
||||
case SpvExecutionModelTessellationEvaluation: {
|
||||
// Load and store InvocationId. Second word is unused; store zero.
|
||||
GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInvocationId),
|
||||
kInstTessOutInvocationId, base_offset_id, builder);
|
||||
GenUintNullOutputCode(kInstTessOutUnused, base_offset_id, builder);
|
||||
} break;
|
||||
case SpvExecutionModelFragment: {
|
||||
// Load FragCoord and convert to Uint
|
||||
Instruction* frag_coord_inst =
|
||||
builder->AddUnaryOp(GetVec4FloatId(), SpvOpLoad,
|
||||
context()->GetBuiltinVarId(SpvBuiltInFragCoord));
|
||||
Instruction* uint_frag_coord_inst = builder->AddUnaryOp(
|
||||
GetVec4UintId(), SpvOpBitcast, frag_coord_inst->result_id());
|
||||
for (uint32_t u = 0; u < 2u; ++u)
|
||||
GenFragCoordEltDebugOutputCode(
|
||||
base_offset_id, uint_frag_coord_inst->result_id(), u, builder);
|
||||
} break;
|
||||
default: { assert(false && "unsupported stage"); } break;
|
||||
}
|
||||
}
|
||||
|
||||
void InstrumentPass::GenDebugStreamWrite(
|
||||
uint32_t instruction_idx, uint32_t stage_idx,
|
||||
const std::vector<uint32_t>& validation_ids, InstructionBuilder* builder) {
|
||||
// Call debug output function. Pass func_idx, instruction_idx and
|
||||
// validation ids as args.
|
||||
uint32_t val_id_cnt = static_cast<uint32_t>(validation_ids.size());
|
||||
uint32_t output_func_id = GetStreamWriteFunctionId(stage_idx, val_id_cnt);
|
||||
std::vector<uint32_t> args = {output_func_id,
|
||||
builder->GetUintConstantId(instruction_idx)};
|
||||
(void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
|
||||
(void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
|
||||
}
|
||||
|
||||
bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
|
||||
return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
|
||||
}
|
||||
|
||||
void InstrumentPass::CloneSameBlockOps(
|
||||
std::unique_ptr<Instruction>* inst,
|
||||
std::unordered_map<uint32_t, uint32_t>* same_blk_post,
|
||||
std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
|
||||
std::unique_ptr<BasicBlock>* block_ptr) {
|
||||
(*inst)->ForEachInId(
|
||||
[&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) {
|
||||
const auto map_itr = (*same_blk_post).find(*iid);
|
||||
if (map_itr == (*same_blk_post).end()) {
|
||||
const auto map_itr2 = (*same_blk_pre).find(*iid);
|
||||
if (map_itr2 != (*same_blk_pre).end()) {
|
||||
// Clone pre-call same-block ops, map result id.
|
||||
const Instruction* in_inst = map_itr2->second;
|
||||
std::unique_ptr<Instruction> sb_inst(in_inst->Clone(context()));
|
||||
CloneSameBlockOps(&sb_inst, same_blk_post, same_blk_pre, block_ptr);
|
||||
const uint32_t rid = sb_inst->result_id();
|
||||
const uint32_t nid = this->TakeNextId();
|
||||
get_decoration_mgr()->CloneDecorations(rid, nid);
|
||||
sb_inst->SetResultId(nid);
|
||||
(*same_blk_post)[rid] = nid;
|
||||
*iid = nid;
|
||||
(*block_ptr)->AddInstruction(std::move(sb_inst));
|
||||
}
|
||||
} else {
|
||||
// Reset same-block op operand.
|
||||
*iid = map_itr->second;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InstrumentPass::UpdateSucceedingPhis(
|
||||
std::vector<std::unique_ptr<BasicBlock>>& new_blocks) {
|
||||
const auto first_blk = new_blocks.begin();
|
||||
const auto last_blk = new_blocks.end() - 1;
|
||||
const uint32_t first_id = (*first_blk)->id();
|
||||
const uint32_t last_id = (*last_blk)->id();
|
||||
const BasicBlock& const_last_block = *last_blk->get();
|
||||
const_last_block.ForEachSuccessorLabel(
|
||||
[&first_id, &last_id, this](const uint32_t succ) {
|
||||
BasicBlock* sbp = this->id2block_[succ];
|
||||
sbp->ForEachPhiInst([&first_id, &last_id, this](Instruction* phi) {
|
||||
bool changed = false;
|
||||
phi->ForEachInId([&first_id, &last_id, &changed](uint32_t* id) {
|
||||
if (*id == first_id) {
|
||||
*id = last_id;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) get_def_use_mgr()->AnalyzeInstUse(phi);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Return id for output buffer uint ptr type
|
||||
uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
|
||||
if (output_buffer_uint_ptr_id_ == 0) {
|
||||
output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
|
||||
GetUintId(), SpvStorageClassStorageBuffer);
|
||||
}
|
||||
return output_buffer_uint_ptr_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetOutputBufferBinding() {
|
||||
switch (validation_id_) {
|
||||
case kInstValidationIdBindless:
|
||||
return kDebugOutputBindingStream;
|
||||
default:
|
||||
assert(false && "unexpected validation id");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return id for output buffer
|
||||
uint32_t InstrumentPass::GetOutputBufferId() {
|
||||
if (output_buffer_id_ == 0) {
|
||||
// If not created yet, create one
|
||||
analysis::DecorationManager* deco_mgr = get_decoration_mgr();
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Integer uint_ty(32, false);
|
||||
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
|
||||
analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
|
||||
analysis::Type* reg_uint_rarr_ty =
|
||||
type_mgr->GetRegisteredType(&uint_rarr_ty);
|
||||
analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
|
||||
analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
|
||||
uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
|
||||
deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
|
||||
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
|
||||
SpvDecorationOffset, 0);
|
||||
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputDataOffset,
|
||||
SpvDecorationOffset, 4);
|
||||
uint32_t obufTyPtrId_ =
|
||||
type_mgr->FindPointerToType(obufTyId, SpvStorageClassStorageBuffer);
|
||||
output_buffer_id_ = TakeNextId();
|
||||
std::unique_ptr<Instruction> newVarOp(new Instruction(
|
||||
context(), SpvOpVariable, obufTyPtrId_, output_buffer_id_,
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||
{SpvStorageClassStorageBuffer}}}));
|
||||
context()->AddGlobalValue(std::move(newVarOp));
|
||||
deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationDescriptorSet,
|
||||
desc_set_);
|
||||
deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
|
||||
GetOutputBufferBinding());
|
||||
// Look for storage buffer extension. If none, create one.
|
||||
if (!get_feature_mgr()->HasExtension(
|
||||
kSPV_KHR_storage_buffer_storage_class)) {
|
||||
const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
|
||||
const auto num_chars = ext_name.size();
|
||||
// Compute num words, accommodate the terminating null character.
|
||||
const auto num_words = (num_chars + 1 + 3) / 4;
|
||||
std::vector<uint32_t> ext_words(num_words, 0u);
|
||||
std::memcpy(ext_words.data(), ext_name.data(), num_chars);
|
||||
context()->AddExtension(std::unique_ptr<Instruction>(
|
||||
new Instruction(context(), SpvOpExtension, 0u, 0u,
|
||||
{{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
|
||||
}
|
||||
}
|
||||
return output_buffer_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVec4FloatId() {
|
||||
if (v4float_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Float float_ty(32);
|
||||
analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
|
||||
analysis::Vector v4float_ty(reg_float_ty, 4);
|
||||
analysis::Type* reg_v4float_ty = type_mgr->GetRegisteredType(&v4float_ty);
|
||||
v4float_id_ = type_mgr->GetTypeInstruction(reg_v4float_ty);
|
||||
}
|
||||
return v4float_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetUintId() {
|
||||
if (uint_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Integer uint_ty(32, false);
|
||||
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
|
||||
uint_id_ = type_mgr->GetTypeInstruction(reg_uint_ty);
|
||||
}
|
||||
return uint_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVec4UintId() {
|
||||
if (v4uint_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Integer uint_ty(32, false);
|
||||
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
|
||||
analysis::Vector v4uint_ty(reg_uint_ty, 4);
|
||||
analysis::Type* reg_v4uint_ty = type_mgr->GetRegisteredType(&v4uint_ty);
|
||||
v4uint_id_ = type_mgr->GetTypeInstruction(reg_v4uint_ty);
|
||||
}
|
||||
return v4uint_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetBoolId() {
|
||||
if (bool_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Bool bool_ty;
|
||||
analysis::Type* reg_bool_ty = type_mgr->GetRegisteredType(&bool_ty);
|
||||
bool_id_ = type_mgr->GetTypeInstruction(reg_bool_ty);
|
||||
}
|
||||
return bool_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVoidId() {
|
||||
if (void_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Void void_ty;
|
||||
analysis::Type* reg_void_ty = type_mgr->GetRegisteredType(&void_ty);
|
||||
void_id_ = type_mgr->GetTypeInstruction(reg_void_ty);
|
||||
}
|
||||
return void_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx,
|
||||
uint32_t val_spec_param_cnt) {
|
||||
// Total param count is common params plus validation-specific
|
||||
// params
|
||||
uint32_t param_cnt = kInstCommonParamCnt + val_spec_param_cnt;
|
||||
if (output_func_id_ == 0) {
|
||||
// Create function
|
||||
output_func_id_ = TakeNextId();
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
std::vector<const analysis::Type*> param_types;
|
||||
for (uint32_t c = 0; c < param_cnt; ++c)
|
||||
param_types.push_back(type_mgr->GetType(GetUintId()));
|
||||
analysis::Function func_ty(type_mgr->GetType(GetVoidId()), param_types);
|
||||
analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
|
||||
std::unique_ptr<Instruction> func_inst(new Instruction(
|
||||
get_module()->context(), SpvOpFunction, GetVoidId(), output_func_id_,
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||
{SpvFunctionControlMaskNone}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
|
||||
{type_mgr->GetTypeInstruction(reg_func_ty)}}}));
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
|
||||
std::unique_ptr<Function> output_func =
|
||||
MakeUnique<Function>(std::move(func_inst));
|
||||
// Add parameters
|
||||
std::vector<uint32_t> param_vec;
|
||||
for (uint32_t c = 0; c < param_cnt; ++c) {
|
||||
uint32_t pid = TakeNextId();
|
||||
param_vec.push_back(pid);
|
||||
std::unique_ptr<Instruction> param_inst(
|
||||
new Instruction(get_module()->context(), SpvOpFunctionParameter,
|
||||
GetUintId(), pid, {}));
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
|
||||
output_func->AddParameter(std::move(param_inst));
|
||||
}
|
||||
// Create first block
|
||||
uint32_t test_blk_id = TakeNextId();
|
||||
std::unique_ptr<Instruction> test_label(NewLabel(test_blk_id));
|
||||
std::unique_ptr<BasicBlock> new_blk_ptr =
|
||||
MakeUnique<BasicBlock>(std::move(test_label));
|
||||
InstructionBuilder builder(
|
||||
context(), &*new_blk_ptr,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
// Gen test if debug output buffer size will not be exceeded.
|
||||
uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
|
||||
uint32_t buf_id = GetOutputBufferId();
|
||||
uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
|
||||
Instruction* obuf_curr_sz_ac_inst =
|
||||
builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
|
||||
builder.GetUintConstantId(kDebugOutputSizeOffset));
|
||||
// Fetch the current debug buffer written size atomically, adding the
|
||||
// size of the record to be written.
|
||||
uint32_t obuf_record_sz_id = builder.GetUintConstantId(obuf_record_sz);
|
||||
uint32_t mask_none_id = builder.GetUintConstantId(SpvMemoryAccessMaskNone);
|
||||
uint32_t scope_invok_id = builder.GetUintConstantId(SpvScopeInvocation);
|
||||
Instruction* obuf_curr_sz_inst = builder.AddQuadOp(
|
||||
GetUintId(), SpvOpAtomicIAdd, obuf_curr_sz_ac_inst->result_id(),
|
||||
scope_invok_id, mask_none_id, obuf_record_sz_id);
|
||||
uint32_t obuf_curr_sz_id = obuf_curr_sz_inst->result_id();
|
||||
// Compute new written size
|
||||
Instruction* obuf_new_sz_inst =
|
||||
builder.AddBinaryOp(GetUintId(), SpvOpIAdd, obuf_curr_sz_id,
|
||||
builder.GetUintConstantId(obuf_record_sz));
|
||||
// Fetch the data bound
|
||||
Instruction* obuf_bnd_inst =
|
||||
builder.AddIdLiteralOp(GetUintId(), SpvOpArrayLength,
|
||||
GetOutputBufferId(), kDebugOutputDataOffset);
|
||||
// Test that new written size is less than or equal to debug output
|
||||
// data bound
|
||||
Instruction* obuf_safe_inst = builder.AddBinaryOp(
|
||||
GetBoolId(), SpvOpULessThanEqual, obuf_new_sz_inst->result_id(),
|
||||
obuf_bnd_inst->result_id());
|
||||
uint32_t merge_blk_id = TakeNextId();
|
||||
uint32_t write_blk_id = TakeNextId();
|
||||
std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
|
||||
std::unique_ptr<Instruction> write_label(NewLabel(write_blk_id));
|
||||
(void)builder.AddConditionalBranch(obuf_safe_inst->result_id(),
|
||||
write_blk_id, merge_blk_id, merge_blk_id,
|
||||
SpvSelectionControlMaskNone);
|
||||
// Close safety test block and gen write block
|
||||
new_blk_ptr->SetParent(&*output_func);
|
||||
output_func->AddBasicBlock(std::move(new_blk_ptr));
|
||||
new_blk_ptr = MakeUnique<BasicBlock>(std::move(write_label));
|
||||
builder.SetInsertPoint(&*new_blk_ptr);
|
||||
// Generate common and stage-specific debug record members
|
||||
GenCommonStreamWriteCode(obuf_record_sz, param_vec[kInstCommonParamInstIdx],
|
||||
stage_idx, obuf_curr_sz_id, &builder);
|
||||
GenStageStreamWriteCode(stage_idx, obuf_curr_sz_id, &builder);
|
||||
// Gen writes of validation specific data
|
||||
for (uint32_t i = 0; i < val_spec_param_cnt; ++i) {
|
||||
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstStageOutCnt + i,
|
||||
param_vec[kInstCommonParamCnt + i], &builder);
|
||||
}
|
||||
// Close write block and gen merge block
|
||||
(void)builder.AddBranch(merge_blk_id);
|
||||
new_blk_ptr->SetParent(&*output_func);
|
||||
output_func->AddBasicBlock(std::move(new_blk_ptr));
|
||||
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
||||
builder.SetInsertPoint(&*new_blk_ptr);
|
||||
// Close merge block and function and add function to module
|
||||
(void)builder.AddNullaryOp(0, SpvOpReturn);
|
||||
new_blk_ptr->SetParent(&*output_func);
|
||||
output_func->AddBasicBlock(std::move(new_blk_ptr));
|
||||
std::unique_ptr<Instruction> func_end_inst(
|
||||
new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {}));
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
|
||||
output_func->SetFunctionEnd(std::move(func_end_inst));
|
||||
context()->AddFunction(std::move(output_func));
|
||||
output_func_param_cnt_ = param_cnt;
|
||||
}
|
||||
assert(param_cnt == output_func_param_cnt_ && "bad arg count");
|
||||
return output_func_id_;
|
||||
}
|
||||
|
||||
bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
|
||||
InstProcessFunction& pfn) {
|
||||
bool modified = false;
|
||||
// Compute function index
|
||||
uint32_t function_idx = 0;
|
||||
for (auto fii = get_module()->begin(); fii != get_module()->end(); ++fii) {
|
||||
if (&*fii == func) break;
|
||||
++function_idx;
|
||||
}
|
||||
std::vector<std::unique_ptr<BasicBlock>> new_blks;
|
||||
// Start count after function instruction
|
||||
uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
|
||||
// Using block iterators here because of block erasures and insertions.
|
||||
for (auto bi = func->begin(); bi != func->end(); ++bi) {
|
||||
// Count block's label
|
||||
++instruction_idx;
|
||||
for (auto ii = bi->begin(); ii != bi->end(); ++instruction_idx) {
|
||||
// Bump instruction count if debug instructions
|
||||
instruction_idx += static_cast<uint32_t>(ii->dbg_line_insts().size());
|
||||
// Generate instrumentation if warranted
|
||||
pfn(ii, bi, instruction_idx, stage_idx, &new_blks);
|
||||
if (new_blks.size() == 0) {
|
||||
++ii;
|
||||
continue;
|
||||
}
|
||||
// If there are new blocks we know there will always be two or
|
||||
// more, so update succeeding phis with label of new last block.
|
||||
size_t newBlocksSize = new_blks.size();
|
||||
assert(newBlocksSize > 1);
|
||||
UpdateSucceedingPhis(new_blks);
|
||||
// Replace original block with new block(s)
|
||||
bi = bi.Erase();
|
||||
for (auto& bb : new_blks) {
|
||||
bb->SetParent(func);
|
||||
}
|
||||
bi = bi.InsertBefore(&new_blks);
|
||||
// Reset block iterator to last new block
|
||||
for (size_t i = 0; i < newBlocksSize - 1; i++) ++bi;
|
||||
modified = true;
|
||||
// Restart instrumenting at beginning of last new block,
|
||||
// but skip over any new phi or copy instruction.
|
||||
ii = bi->begin();
|
||||
if (ii->opcode() == SpvOpPhi || ii->opcode() == SpvOpCopyObject) ++ii;
|
||||
new_blks.clear();
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
|
||||
std::queue<uint32_t>* roots,
|
||||
uint32_t stage_idx) {
|
||||
bool modified = false;
|
||||
std::unordered_set<uint32_t> done;
|
||||
// Process all functions from roots
|
||||
while (!roots->empty()) {
|
||||
const uint32_t fi = roots->front();
|
||||
roots->pop();
|
||||
if (done.insert(fi).second) {
|
||||
Function* fn = id2function_.at(fi);
|
||||
// Add calls first so we don't add new output function
|
||||
AddCalls(fn, roots);
|
||||
modified = InstrumentFunction(fn, stage_idx, pfn) || modified;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
|
||||
// Make sure all entry points have the same execution model. Do not
|
||||
// instrument if they do not.
|
||||
// TODO(greg-lunarg): Handle mixed stages. Technically, a shader module
|
||||
// can contain entry points with different execution models, although
|
||||
// such modules will likely be rare as GLSL and HLSL are geared toward
|
||||
// one model per module. In such cases we will need
|
||||
// to clone any functions which are in the call trees of entrypoints
|
||||
// with differing execution models.
|
||||
uint32_t ecnt = 0;
|
||||
uint32_t stage = SpvExecutionModelMax;
|
||||
for (auto& e : get_module()->entry_points()) {
|
||||
if (ecnt == 0)
|
||||
stage = e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx);
|
||||
else if (e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx) != stage)
|
||||
return false;
|
||||
++ecnt;
|
||||
}
|
||||
// Only supporting vertex, fragment and compute shaders at the moment.
|
||||
// TODO(greg-lunarg): Handle all stages.
|
||||
if (stage != SpvExecutionModelVertex && stage != SpvExecutionModelFragment &&
|
||||
stage != SpvExecutionModelGeometry &&
|
||||
stage != SpvExecutionModelGLCompute &&
|
||||
stage != SpvExecutionModelTessellationControl &&
|
||||
stage != SpvExecutionModelTessellationEvaluation)
|
||||
return false;
|
||||
// Add together the roots of all entry points
|
||||
std::queue<uint32_t> roots;
|
||||
for (auto& e : get_module()->entry_points()) {
|
||||
roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
|
||||
}
|
||||
bool modified = InstProcessCallTreeFromRoots(pfn, &roots, stage);
|
||||
return modified;
|
||||
}
|
||||
|
||||
void InstrumentPass::InitializeInstrument() {
|
||||
output_buffer_id_ = 0;
|
||||
output_buffer_uint_ptr_id_ = 0;
|
||||
output_func_id_ = 0;
|
||||
output_func_param_cnt_ = 0;
|
||||
v4float_id_ = 0;
|
||||
uint_id_ = 0;
|
||||
v4uint_id_ = 0;
|
||||
bool_id_ = 0;
|
||||
void_id_ = 0;
|
||||
|
||||
// clear collections
|
||||
id2function_.clear();
|
||||
id2block_.clear();
|
||||
|
||||
// Initialize function and block maps.
|
||||
for (auto& fn : *get_module()) {
|
||||
id2function_[fn.result_id()] = &fn;
|
||||
for (auto& blk : fn) {
|
||||
id2block_[blk.id()] = &blk;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate instruction offset of first function
|
||||
uint32_t pre_func_size = 0;
|
||||
Module* module = get_module();
|
||||
for (auto& i : context()->capabilities()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
for (auto& i : module->extensions()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
for (auto& i : module->ext_inst_imports()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
++pre_func_size; // memory_model
|
||||
for (auto& i : module->entry_points()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
for (auto& i : module->execution_modes()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
for (auto& i : module->debugs1()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
for (auto& i : module->debugs2()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
for (auto& i : module->debugs3()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
for (auto& i : module->annotations()) {
|
||||
(void)i;
|
||||
++pre_func_size;
|
||||
}
|
||||
for (auto& i : module->types_values()) {
|
||||
pre_func_size += 1;
|
||||
pre_func_size += static_cast<uint32_t>(i.dbg_line_insts().size());
|
||||
}
|
||||
funcIdx2offset_[0] = pre_func_size;
|
||||
|
||||
// Set instruction offsets for all other functions.
|
||||
uint32_t func_idx = 1;
|
||||
auto prev_fn = get_module()->begin();
|
||||
auto curr_fn = prev_fn;
|
||||
for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
|
||||
// Count function and end instructions
|
||||
uint32_t func_size = 2;
|
||||
for (auto& blk : *prev_fn) {
|
||||
// Count label
|
||||
func_size += 1;
|
||||
for (auto& inst : blk) {
|
||||
func_size += 1;
|
||||
func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
|
||||
}
|
||||
}
|
||||
funcIdx2offset_[func_idx] = func_size;
|
||||
++prev_fn;
|
||||
++func_idx;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
356
source/opt/instrument_pass.h
Normal file
356
source/opt/instrument_pass.h
Normal file
@ -0,0 +1,356 @@
|
||||
// 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_INSTRUMENT_PASS_H_
|
||||
#define LIBSPIRV_OPT_INSTRUMENT_PASS_H_
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "source/opt/ir_builder.h"
|
||||
#include "source/opt/pass.h"
|
||||
#include "spirv-tools/instrument.hpp"
|
||||
|
||||
// This is a base class to assist in the creation of passes which instrument
|
||||
// shader modules. More specifically, passes which replace instructions with a
|
||||
// larger and more capable set of instructions. Commonly, these new
|
||||
// instructions will add testing of operands and execute different
|
||||
// instructions depending on the outcome, including outputting of debug
|
||||
// information into a buffer created especially for that purpose.
|
||||
//
|
||||
// This class contains helper functions to create an InstProcessFunction,
|
||||
// which is the heart of any derived class implementing a specific
|
||||
// instrumentation pass. It takes an instruction as an argument, decides
|
||||
// if it should be instrumented, and generates code to replace it. This class
|
||||
// also supplies function InstProcessEntryPointCallTree which applies the
|
||||
// InstProcessFunction to every reachable instruction in a module and replaces
|
||||
// the instruction with new instructions if generated.
|
||||
//
|
||||
// Chief among the helper functions are output code generation functions,
|
||||
// used to generate code in the shader which writes data to output buffers
|
||||
// associated with that validation. Currently one such function,
|
||||
// GenDebugStreamWrite, exists. Other such functions may be added in the
|
||||
// future. Each is accompanied by documentation describing the format of
|
||||
// its output buffer.
|
||||
//
|
||||
// A validation pass may read or write multiple buffers. All such buffers
|
||||
// are located in a single debug descriptor set whose index is passed at the
|
||||
// creation of the instrumentation pass. The bindings of the buffers used by
|
||||
// a validation pass are permanantly assigned and fixed and documented by
|
||||
// the kDebugOutput* static consts.
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// Validation Ids
|
||||
// These are used to identify the general validation being done and map to
|
||||
// its output buffers.
|
||||
static const uint32_t kInstValidationIdBindless = 0;
|
||||
|
||||
class InstrumentPass : public Pass {
|
||||
using cbb_ptr = const BasicBlock*;
|
||||
|
||||
public:
|
||||
using InstProcessFunction = std::function<void(
|
||||
BasicBlock::iterator, UptrVectorIterator<BasicBlock>, uint32_t, uint32_t,
|
||||
std::vector<std::unique_ptr<BasicBlock>>*)>;
|
||||
|
||||
virtual ~InstrumentPass() = default;
|
||||
|
||||
IRContext::Analysis GetPreservedAnalyses() override {
|
||||
return IRContext::kAnalysisDefUse |
|
||||
IRContext::kAnalysisInstrToBlockMapping |
|
||||
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
|
||||
IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Create instrumentation pass which utilizes descriptor set |desc_set|
|
||||
// for debug input and output buffers and writes |shader_id| into debug
|
||||
// output records.
|
||||
InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id)
|
||||
: Pass(),
|
||||
desc_set_(desc_set),
|
||||
shader_id_(shader_id),
|
||||
validation_id_(validation_id) {}
|
||||
|
||||
// Initialize state for instrumentation of module by |validation_id|.
|
||||
void InitializeInstrument();
|
||||
|
||||
// Call |pfn| on all instructions in all functions in the call tree of the
|
||||
// entry points in |module|. If code is generated for an instruction, replace
|
||||
// the instruction's block with the new blocks that are generated. Continue
|
||||
// processing at the top of the last new block.
|
||||
bool InstProcessEntryPointCallTree(InstProcessFunction& pfn);
|
||||
|
||||
// Move all code in |ref_block_itr| preceding the instruction |ref_inst_itr|
|
||||
// to be instrumented into block |new_blk_ptr|.
|
||||
void MovePreludeCode(BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
std::unique_ptr<BasicBlock>* new_blk_ptr);
|
||||
|
||||
// Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr|
|
||||
// to be instrumented into block |new_blk_ptr|.
|
||||
void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
std::unique_ptr<BasicBlock>* new_blk_ptr);
|
||||
|
||||
// Generate instructions in |builder| which will atomically fetch and
|
||||
// increment the size of the debug output buffer stream of the current
|
||||
// validation and write a record to the end of the stream, if enough space
|
||||
// in the buffer remains. The record will contain the index of the function
|
||||
// and instruction within that function |func_idx, instruction_idx| which
|
||||
// generated the record. It will also contain additional information to
|
||||
// identify the instance of the shader, depending on the stage |stage_idx|
|
||||
// of the shader. Finally, the record will contain validation-specific
|
||||
// data contained in |validation_ids| which will identify the validation
|
||||
// error as well as the values involved in the error.
|
||||
//
|
||||
// The output buffer binding written to by the code generated by the function
|
||||
// is determined by the validation id specified when each specific
|
||||
// instrumentation pass is created.
|
||||
//
|
||||
// The output buffer is a sequence of 32-bit values with the following
|
||||
// format (where all elements are unsigned 32-bit unless otherwise noted):
|
||||
//
|
||||
// Size
|
||||
// Record0
|
||||
// Record1
|
||||
// Record2
|
||||
// ...
|
||||
//
|
||||
// Size is the number of 32-bit values that have been written or
|
||||
// attempted to be written to the output buffer, excluding the Size. It is
|
||||
// initialized to 0. If the size of attempts to write the buffer exceeds
|
||||
// the actual size of the buffer, it is possible that this field can exceed
|
||||
// the actual size of the buffer.
|
||||
//
|
||||
// Each Record* is a variable-length sequence of 32-bit values with the
|
||||
// following format defined using static const offsets in the .cpp file:
|
||||
//
|
||||
// Record Size
|
||||
// Shader ID
|
||||
// Instruction Index
|
||||
// Stage
|
||||
// Stage-specific Word 0
|
||||
// Stage-specific Word 1
|
||||
// Validation Error Code
|
||||
// Validation-specific Word 0
|
||||
// Validation-specific Word 1
|
||||
// Validation-specific Word 2
|
||||
// ...
|
||||
//
|
||||
// Each record consists of three subsections: members common across all
|
||||
// validation, members specific to the stage, and members specific to a
|
||||
// validation.
|
||||
//
|
||||
// The Record Size is the number of 32-bit words in the record, including
|
||||
// the Record Size word.
|
||||
//
|
||||
// Shader ID is a value that identifies which shader has generated the
|
||||
// validation error. It is passed when the instrumentation pass is created.
|
||||
//
|
||||
// The Instruction Index is the position of the instruction within the
|
||||
// SPIR-V file which is in error.
|
||||
//
|
||||
// The Stage is the pipeline stage which has generated the error as defined
|
||||
// by the SpvExecutionModel_ enumeration. This is used to interpret the
|
||||
// following Stage-specific words.
|
||||
//
|
||||
// The Stage-specific Words identify which invocation of the shader generated
|
||||
// the error. Every stage will write two words, although in some cases the
|
||||
// second word is unused and so zero is written. Vertex shaders will write
|
||||
// the Vertex and Instance ID. Fragment shaders will write FragCoord.xy.
|
||||
// Compute shaders will write the Global Invocation ID and zero (unused).
|
||||
// Both tesselation shaders will write the Invocation Id and zero (unused).
|
||||
// The geometry shader will write the Primitive ID and Invocation ID.
|
||||
//
|
||||
// The Validation Error Code specifies the exact error which has occurred.
|
||||
// These are enumerated with the kInstError* static consts. This allows
|
||||
// multiple validation layers to use the same, single output buffer.
|
||||
//
|
||||
// The Validation-specific Words are a validation-specific number of 32-bit
|
||||
// words which give further information on the validation error that
|
||||
// occurred. These are documented further in each file containing the
|
||||
// validation-specific class which derives from this base class.
|
||||
//
|
||||
// Because the code that is generated checks against the size of the buffer
|
||||
// before writing, the size of the debug out buffer can be used by the
|
||||
// validation layer to control the number of error records that are written.
|
||||
void GenDebugStreamWrite(uint32_t instruction_idx, uint32_t stage_idx,
|
||||
const std::vector<uint32_t>& validation_ids,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Generate code to cast |value_id| to unsigned, if needed. Return
|
||||
// an id to the unsigned equivalent.
|
||||
uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
|
||||
|
||||
// Return new label.
|
||||
std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
|
||||
|
||||
// Return id for 32-bit unsigned type
|
||||
uint32_t GetUintId();
|
||||
|
||||
// Return id for 32-bit unsigned type
|
||||
uint32_t GetBoolId();
|
||||
|
||||
// Return id for void type
|
||||
uint32_t GetVoidId();
|
||||
|
||||
// Return id for output buffer uint type
|
||||
uint32_t GetOutputBufferUintPtrId();
|
||||
|
||||
// Return binding for output buffer for current validation.
|
||||
uint32_t GetOutputBufferBinding();
|
||||
|
||||
// Return id for debug output buffer
|
||||
uint32_t GetOutputBufferId();
|
||||
|
||||
// Return id for v4float type
|
||||
uint32_t GetVec4FloatId();
|
||||
|
||||
// Return id for v4uint type
|
||||
uint32_t GetVec4UintId();
|
||||
|
||||
// Return id for output function. Define if it doesn't exist with
|
||||
// |val_spec_arg_cnt| validation-specific uint32 arguments.
|
||||
uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
|
||||
uint32_t val_spec_param_cnt);
|
||||
|
||||
// Apply instrumentation function |pfn| to every instruction in |func|.
|
||||
// If code is generated for an instruction, replace the instruction's
|
||||
// block with the new blocks that are generated. Continue processing at the
|
||||
// top of the last new block.
|
||||
bool InstrumentFunction(Function* func, uint32_t stage_idx,
|
||||
InstProcessFunction& pfn);
|
||||
|
||||
// Call |pfn| on all functions in the call tree of the function
|
||||
// ids in |roots|.
|
||||
bool InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
|
||||
std::queue<uint32_t>* roots,
|
||||
uint32_t stage_idx);
|
||||
|
||||
// Gen code into |builder| to write |field_value_id| into debug output
|
||||
// buffer at |base_offset_id| + |field_offset|.
|
||||
void GenDebugOutputFieldCode(uint32_t base_offset_id, uint32_t field_offset,
|
||||
uint32_t field_value_id,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Generate instructions into |builder| which will write the members
|
||||
// of the debug output record common for all stages and validations at
|
||||
// |base_off|.
|
||||
void GenCommonStreamWriteCode(uint32_t record_sz, uint32_t instruction_idx,
|
||||
uint32_t stage_idx, uint32_t base_off,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Generate instructions into |builder| which will write
|
||||
// |uint_frag_coord_id| at |component| of the record at |base_offset_id| of
|
||||
// the debug output buffer .
|
||||
void GenFragCoordEltDebugOutputCode(uint32_t base_offset_id,
|
||||
uint32_t uint_frag_coord_id,
|
||||
uint32_t component,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Generate instructions into |builder| which will load the uint |builtin_id|
|
||||
// and write it into the debug output buffer at |base_off| + |builtin_off|.
|
||||
void GenBuiltinOutputCode(uint32_t builtin_id, uint32_t builtin_off,
|
||||
uint32_t base_off, InstructionBuilder* builder);
|
||||
|
||||
// Generate instructions into |builder| which will write a uint null into
|
||||
// the debug output buffer at |base_off| + |builtin_off|.
|
||||
void GenUintNullOutputCode(uint32_t field_off, uint32_t base_off,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Generate instructions into |builder| which will write the |stage_idx|-
|
||||
// specific members of the debug output stream at |base_off|.
|
||||
void GenStageStreamWriteCode(uint32_t stage_idx, uint32_t base_off,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Return true if instruction must be in the same block that its result
|
||||
// is used.
|
||||
bool IsSameBlockOp(const Instruction* inst) const;
|
||||
|
||||
// Clone operands which must be in same block as consumer instructions.
|
||||
// Look in same_blk_pre for instructions that need cloning. Look in
|
||||
// same_blk_post for instructions already cloned. Add cloned instruction
|
||||
// to same_blk_post.
|
||||
void CloneSameBlockOps(
|
||||
std::unique_ptr<Instruction>* inst,
|
||||
std::unordered_map<uint32_t, uint32_t>* same_blk_post,
|
||||
std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
|
||||
std::unique_ptr<BasicBlock>* block_ptr);
|
||||
|
||||
// Update phis in succeeding blocks to point to new last block
|
||||
void UpdateSucceedingPhis(
|
||||
std::vector<std::unique_ptr<BasicBlock>>& new_blocks);
|
||||
|
||||
// Debug descriptor set index
|
||||
uint32_t desc_set_;
|
||||
|
||||
// Shader module ID written into output record
|
||||
uint32_t shader_id_;
|
||||
|
||||
// Map from function id to function pointer.
|
||||
std::unordered_map<uint32_t, Function*> id2function_;
|
||||
|
||||
// Map from block's label id to block. TODO(dnovillo): This is superfluous wrt
|
||||
// CFG. It has functionality not present in CFG. Consolidate.
|
||||
std::unordered_map<uint32_t, BasicBlock*> id2block_;
|
||||
|
||||
// Map from function's position index to the offset of its first instruction
|
||||
std::unordered_map<uint32_t, uint32_t> funcIdx2offset_;
|
||||
|
||||
// result id for OpConstantFalse
|
||||
uint32_t validation_id_;
|
||||
|
||||
// id for output buffer variable
|
||||
uint32_t output_buffer_id_;
|
||||
|
||||
// type id for output buffer element
|
||||
uint32_t output_buffer_uint_ptr_id_;
|
||||
|
||||
// id for debug output function
|
||||
uint32_t output_func_id_;
|
||||
|
||||
// param count for output function
|
||||
uint32_t output_func_param_cnt_;
|
||||
|
||||
// id for v4float type
|
||||
uint32_t v4float_id_;
|
||||
|
||||
// id for v4float type
|
||||
uint32_t v4uint_id_;
|
||||
|
||||
// id for 32-bit unsigned type
|
||||
uint32_t uint_id_;
|
||||
|
||||
// id for bool type
|
||||
uint32_t bool_id_;
|
||||
|
||||
// id for void type
|
||||
uint32_t void_id_;
|
||||
|
||||
// Pre-instrumentation same-block insts
|
||||
std::unordered_map<uint32_t, Instruction*> same_block_pre_;
|
||||
|
||||
// Post-instrumentation same-block op ids
|
||||
std::unordered_map<uint32_t, uint32_t> same_block_post_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // LIBSPIRV_OPT_INSTRUMENT_PASS_H_
|
@ -58,6 +58,78 @@ class InstructionBuilder {
|
||||
: InstructionBuilder(context, parent_block, parent_block->end(),
|
||||
preserved_analyses) {}
|
||||
|
||||
Instruction* AddNullaryOp(uint32_t type_id, SpvOp opcode) {
|
||||
std::unique_ptr<Instruction> newUnOp(new Instruction(
|
||||
GetContext(), opcode, type_id,
|
||||
opcode == SpvOpReturn ? 0 : GetContext()->TakeNextId(), {}));
|
||||
return AddInstruction(std::move(newUnOp));
|
||||
}
|
||||
|
||||
Instruction* AddUnaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1) {
|
||||
std::unique_ptr<Instruction> newUnOp(new Instruction(
|
||||
GetContext(), opcode, type_id, GetContext()->TakeNextId(),
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}}));
|
||||
return AddInstruction(std::move(newUnOp));
|
||||
}
|
||||
|
||||
Instruction* AddBinaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
|
||||
uint32_t operand2) {
|
||||
std::unique_ptr<Instruction> newBinOp(new Instruction(
|
||||
GetContext(), opcode, type_id,
|
||||
opcode == SpvOpStore ? 0 : GetContext()->TakeNextId(),
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}}}));
|
||||
return AddInstruction(std::move(newBinOp));
|
||||
}
|
||||
|
||||
Instruction* AddTernaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
|
||||
uint32_t operand2, uint32_t operand3) {
|
||||
std::unique_ptr<Instruction> newTernOp(new Instruction(
|
||||
GetContext(), opcode, type_id, GetContext()->TakeNextId(),
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand3}}}));
|
||||
return AddInstruction(std::move(newTernOp));
|
||||
}
|
||||
|
||||
Instruction* AddQuadOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
|
||||
uint32_t operand2, uint32_t operand3,
|
||||
uint32_t operand4) {
|
||||
std::unique_ptr<Instruction> newQuadOp(new Instruction(
|
||||
GetContext(), opcode, type_id, GetContext()->TakeNextId(),
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand3}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand4}}}));
|
||||
return AddInstruction(std::move(newQuadOp));
|
||||
}
|
||||
|
||||
Instruction* AddIdLiteralOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
|
||||
uint32_t operand2) {
|
||||
std::unique_ptr<Instruction> newBinOp(new Instruction(
|
||||
GetContext(), opcode, type_id, GetContext()->TakeNextId(),
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {operand2}}}));
|
||||
return AddInstruction(std::move(newBinOp));
|
||||
}
|
||||
|
||||
// Creates an N-ary instruction of |opcode|.
|
||||
// |typid| must be the id of the instruction's type.
|
||||
// |operands| must be a sequence of operand ids.
|
||||
// Use |result| for the result id if non-zero.
|
||||
Instruction* AddNaryOp(uint32_t type_id, SpvOp opcode,
|
||||
const std::vector<uint32_t>& operands,
|
||||
uint32_t result = 0) {
|
||||
std::vector<Operand> ops;
|
||||
for (size_t i = 0; i < operands.size(); i++) {
|
||||
ops.push_back({SPV_OPERAND_TYPE_ID, {operands[i]}});
|
||||
}
|
||||
std::unique_ptr<Instruction> new_inst(new Instruction(
|
||||
GetContext(), opcode, type_id,
|
||||
result != 0 ? result : GetContext()->TakeNextId(), ops));
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
// Creates a new selection merge instruction.
|
||||
// The id |merge_id| is the merge basic block id.
|
||||
Instruction* AddSelectionMerge(
|
||||
@ -167,15 +239,10 @@ class InstructionBuilder {
|
||||
// The id |type| must be the id of the phi instruction's type.
|
||||
// The vector |incomings| must be a sequence of pairs of <definition id,
|
||||
// parent id>.
|
||||
Instruction* AddPhi(uint32_t type, const std::vector<uint32_t>& incomings) {
|
||||
Instruction* AddPhi(uint32_t type, const std::vector<uint32_t>& incomings,
|
||||
uint32_t result = 0) {
|
||||
assert(incomings.size() % 2 == 0 && "A sequence of pairs is expected");
|
||||
std::vector<Operand> phi_ops;
|
||||
for (size_t i = 0; i < incomings.size(); i++) {
|
||||
phi_ops.push_back({SPV_OPERAND_TYPE_ID, {incomings[i]}});
|
||||
}
|
||||
std::unique_ptr<Instruction> phi_inst(new Instruction(
|
||||
GetContext(), SpvOpPhi, type, GetContext()->TakeNextId(), phi_ops));
|
||||
return AddInstruction(std::move(phi_inst));
|
||||
return AddNaryOp(type, SpvOpPhi, incomings, result);
|
||||
}
|
||||
|
||||
// Creates an addition instruction.
|
||||
@ -249,8 +316,8 @@ class InstructionBuilder {
|
||||
|
||||
// Adds a signed int32 constant to the binary.
|
||||
// The |value| parameter is the constant value to be added.
|
||||
Instruction* Add32BitSignedIntegerConstant(int32_t value) {
|
||||
return Add32BitConstantInteger<int32_t>(value, true);
|
||||
Instruction* GetSintConstant(int32_t value) {
|
||||
return GetIntConstant<int32_t>(value, true);
|
||||
}
|
||||
|
||||
// Create a composite construct.
|
||||
@ -270,8 +337,23 @@ class InstructionBuilder {
|
||||
}
|
||||
// Adds an unsigned int32 constant to the binary.
|
||||
// The |value| parameter is the constant value to be added.
|
||||
Instruction* Add32BitUnsignedIntegerConstant(uint32_t value) {
|
||||
return Add32BitConstantInteger<uint32_t>(value, false);
|
||||
Instruction* GetUintConstant(uint32_t value) {
|
||||
return GetIntConstant<uint32_t>(value, false);
|
||||
}
|
||||
|
||||
uint32_t GetUintConstantId(uint32_t value) {
|
||||
Instruction* uint_inst = GetUintConstant(value);
|
||||
return uint_inst->result_id();
|
||||
}
|
||||
|
||||
uint32_t GetNullId(uint32_t type_id) {
|
||||
analysis::TypeManager* type_mgr = GetContext()->get_type_mgr();
|
||||
analysis::ConstantManager* const_mgr = GetContext()->get_constant_mgr();
|
||||
const analysis::Type* type = type_mgr->GetType(type_id);
|
||||
const analysis::Constant* null_const = const_mgr->GetConstant(type, {});
|
||||
Instruction* null_inst =
|
||||
const_mgr->GetDefiningInstruction(null_const, type_id);
|
||||
return null_inst->result_id();
|
||||
}
|
||||
|
||||
// Adds either a signed or unsigned 32 bit integer constant to the binary
|
||||
@ -279,7 +361,7 @@ class InstructionBuilder {
|
||||
// signed constant otherwise as an unsigned constant. If |sign| is false the
|
||||
// value must not be a negative number.
|
||||
template <typename T>
|
||||
Instruction* Add32BitConstantInteger(T value, bool sign) {
|
||||
Instruction* GetIntConstant(T value, bool sign) {
|
||||
// Assert that we are not trying to store a negative number in an unsigned
|
||||
// type.
|
||||
if (!sign)
|
||||
|
@ -21,6 +21,15 @@
|
||||
#include "source/opt/mem_pass.h"
|
||||
#include "source/opt/reflect.h"
|
||||
|
||||
namespace {
|
||||
|
||||
static const int kSpvDecorateTargetIdInIdx = 0;
|
||||
static const int kSpvDecorateDecorationInIdx = 1;
|
||||
static const int kSpvDecorateBuiltinInIdx = 2;
|
||||
static const int kEntryPointInterfaceInIdx = 3;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
@ -43,6 +52,9 @@ void IRContext::BuildInvalidAnalyses(IRContext::Analysis set) {
|
||||
if (set & kAnalysisLoopAnalysis) {
|
||||
ResetLoopAnalysis();
|
||||
}
|
||||
if (set & kAnalysisBuiltinVarId) {
|
||||
ResetBuiltinAnalysis();
|
||||
}
|
||||
if (set & kAnalysisNameMap) {
|
||||
BuildIdToNameMap();
|
||||
}
|
||||
@ -79,6 +91,9 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
|
||||
if (analyses_to_invalidate & kAnalysisCombinators) {
|
||||
combinator_ops_.clear();
|
||||
}
|
||||
if (analyses_to_invalidate & kAnalysisBuiltinVarId) {
|
||||
builtin_var_id_map_.clear();
|
||||
}
|
||||
if (analyses_to_invalidate & kAnalysisCFG) {
|
||||
cfg_.reset(nullptr);
|
||||
}
|
||||
@ -573,6 +588,91 @@ LoopDescriptor* IRContext::GetLoopDescriptor(const Function* f) {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
uint32_t IRContext::FindBuiltinVar(uint32_t builtin) {
|
||||
for (auto& a : module_->annotations()) {
|
||||
if (a.opcode() != SpvOpDecorate) continue;
|
||||
if (a.GetSingleWordInOperand(kSpvDecorateDecorationInIdx) !=
|
||||
SpvDecorationBuiltIn)
|
||||
continue;
|
||||
if (a.GetSingleWordInOperand(kSpvDecorateBuiltinInIdx) != builtin) continue;
|
||||
uint32_t target_id = a.GetSingleWordInOperand(kSpvDecorateTargetIdInIdx);
|
||||
Instruction* b_var = get_def_use_mgr()->GetDef(target_id);
|
||||
if (b_var->opcode() != SpvOpVariable) continue;
|
||||
return target_id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IRContext::AddVarToEntryPoints(uint32_t var_id) {
|
||||
uint32_t ocnt = 0;
|
||||
for (auto& e : module()->entry_points()) {
|
||||
bool found = false;
|
||||
e.ForEachInOperand([&ocnt, &found, &var_id](const uint32_t* idp) {
|
||||
if (ocnt >= kEntryPointInterfaceInIdx) {
|
||||
if (*idp == var_id) found = true;
|
||||
}
|
||||
++ocnt;
|
||||
});
|
||||
if (!found) {
|
||||
e.AddOperand({SPV_OPERAND_TYPE_ID, {var_id}});
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t IRContext::GetBuiltinVarId(uint32_t builtin) {
|
||||
if (!AreAnalysesValid(kAnalysisBuiltinVarId)) ResetBuiltinAnalysis();
|
||||
// If cached, return it.
|
||||
std::unordered_map<uint32_t, uint32_t>::iterator it =
|
||||
builtin_var_id_map_.find(builtin);
|
||||
if (it != builtin_var_id_map_.end()) return it->second;
|
||||
// Look for one in shader
|
||||
uint32_t var_id = FindBuiltinVar(builtin);
|
||||
if (var_id == 0) {
|
||||
// If not found, create it
|
||||
// TODO(greg-lunarg): Add support for all builtins
|
||||
analysis::TypeManager* type_mgr = get_type_mgr();
|
||||
analysis::Type* reg_type;
|
||||
switch (builtin) {
|
||||
case SpvBuiltInFragCoord: {
|
||||
analysis::Float float_ty(32);
|
||||
analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
|
||||
analysis::Vector v4float_ty(reg_float_ty, 4);
|
||||
reg_type = type_mgr->GetRegisteredType(&v4float_ty);
|
||||
break;
|
||||
}
|
||||
case SpvBuiltInVertexId:
|
||||
case SpvBuiltInInstanceId:
|
||||
case SpvBuiltInPrimitiveId:
|
||||
case SpvBuiltInInvocationId:
|
||||
case SpvBuiltInGlobalInvocationId: {
|
||||
analysis::Integer uint_ty(32, false);
|
||||
reg_type = type_mgr->GetRegisteredType(&uint_ty);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assert(false && "unhandled builtin");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
uint32_t type_id = type_mgr->GetTypeInstruction(reg_type);
|
||||
uint32_t varTyPtrId =
|
||||
type_mgr->FindPointerToType(type_id, SpvStorageClassInput);
|
||||
var_id = TakeNextId();
|
||||
std::unique_ptr<Instruction> newVarOp(
|
||||
new Instruction(this, SpvOpVariable, varTyPtrId, var_id,
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||
{SpvStorageClassInput}}}));
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*newVarOp);
|
||||
module()->AddGlobalValue(std::move(newVarOp));
|
||||
get_decoration_mgr()->AddDecorationVal(var_id, SpvDecorationBuiltIn,
|
||||
builtin);
|
||||
AddVarToEntryPoints(var_id);
|
||||
}
|
||||
builtin_var_id_map_[builtin] = var_id;
|
||||
return var_id;
|
||||
}
|
||||
|
||||
// Gets the dominator analysis for function |f|.
|
||||
DominatorAnalysis* IRContext::GetDominatorAnalysis(const Function* f) {
|
||||
if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) {
|
||||
|
@ -73,7 +73,8 @@ class IRContext {
|
||||
kAnalysisRegisterPressure = 1 << 9,
|
||||
kAnalysisValueNumberTable = 1 << 10,
|
||||
kAnalysisStructuredCFG = 1 << 11,
|
||||
kAnalysisEnd = 1 << 12
|
||||
kAnalysisBuiltinVarId = 1 << 12,
|
||||
kAnalysisEnd = 1 << 13
|
||||
};
|
||||
|
||||
friend inline Analysis operator|(Analysis lhs, Analysis rhs);
|
||||
@ -472,6 +473,11 @@ class IRContext {
|
||||
uint32_t max_id_bound() const { return max_id_bound_; }
|
||||
void set_max_id_bound(uint32_t new_bound) { max_id_bound_ = new_bound; }
|
||||
|
||||
// Return id of variable only decorated with |builtin|, if in module.
|
||||
// Create variable and return its id otherwise. If builtin not currently
|
||||
// supported, return 0.
|
||||
uint32_t GetBuiltinVarId(uint32_t builtin);
|
||||
|
||||
private:
|
||||
// Builds the def-use manager from scratch, even if it was already valid.
|
||||
void BuildDefUseManager() {
|
||||
@ -543,6 +549,13 @@ class IRContext {
|
||||
valid_analyses_ = valid_analyses_ | kAnalysisLoopAnalysis;
|
||||
}
|
||||
|
||||
// Removes all computed loop descriptors.
|
||||
void ResetBuiltinAnalysis() {
|
||||
// Clear the cache.
|
||||
builtin_var_id_map_.clear();
|
||||
valid_analyses_ = valid_analyses_ | kAnalysisBuiltinVarId;
|
||||
}
|
||||
|
||||
// Analyzes the features in the owned module. Builds the manager if required.
|
||||
void AnalyzeFeatures() {
|
||||
feature_mgr_ = MakeUnique<FeatureManager>(grammar_);
|
||||
@ -566,6 +579,13 @@ class IRContext {
|
||||
// true if the cfg is invalidated.
|
||||
bool CheckCFG();
|
||||
|
||||
// Return id of variable only decorated with |builtin|, if in module.
|
||||
// Return 0 otherwise.
|
||||
uint32_t FindBuiltinVar(uint32_t builtin);
|
||||
|
||||
// Add |var_id| to all entry points in module.
|
||||
void AddVarToEntryPoints(uint32_t var_id);
|
||||
|
||||
// The SPIR-V syntax context containing grammar tables for opcodes and
|
||||
// operands.
|
||||
spv_context syntax_context_;
|
||||
@ -607,6 +627,10 @@ class IRContext {
|
||||
// without side-effect.
|
||||
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> combinator_ops_;
|
||||
|
||||
// Opcodes of shader capability core executable instructions
|
||||
// without side-effect.
|
||||
std::unordered_map<uint32_t, uint32_t> builtin_var_id_map_;
|
||||
|
||||
// The CFG for all the functions in |module_|.
|
||||
std::unique_ptr<CFG> cfg_;
|
||||
|
||||
@ -786,6 +810,9 @@ void IRContext::AddCapability(std::unique_ptr<Instruction>&& c) {
|
||||
}
|
||||
|
||||
void IRContext::AddExtension(std::unique_ptr<Instruction>&& e) {
|
||||
if (AreAnalysesValid(kAnalysisDefUse)) {
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(e.get());
|
||||
}
|
||||
module()->AddExtension(std::move(e));
|
||||
}
|
||||
|
||||
@ -827,6 +854,9 @@ void IRContext::AddAnnotationInst(std::unique_ptr<Instruction>&& a) {
|
||||
if (AreAnalysesValid(kAnalysisDecorations)) {
|
||||
get_decoration_mgr()->AddDecoration(a.get());
|
||||
}
|
||||
if (AreAnalysesValid(kAnalysisDefUse)) {
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(a.get());
|
||||
}
|
||||
module()->AddAnnotationInst(std::move(a));
|
||||
}
|
||||
|
||||
@ -838,10 +868,10 @@ void IRContext::AddType(std::unique_ptr<Instruction>&& t) {
|
||||
}
|
||||
|
||||
void IRContext::AddGlobalValue(std::unique_ptr<Instruction>&& v) {
|
||||
module()->AddGlobalValue(std::move(v));
|
||||
if (AreAnalysesValid(kAnalysisDefUse)) {
|
||||
get_def_use_mgr()->AnalyzeInstDef(&*(--types_values_end()));
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*v);
|
||||
}
|
||||
module()->AddGlobalValue(std::move(v));
|
||||
}
|
||||
|
||||
void IRContext::AddFunction(std::unique_ptr<Function>&& f) {
|
||||
|
@ -151,7 +151,7 @@ void LoopPeeling::InsertCanonicalInductionVariable(
|
||||
context_, &*insert_point,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
Instruction* uint_1_cst =
|
||||
builder.Add32BitConstantInteger<uint32_t>(1, int_type_->IsSigned());
|
||||
builder.GetIntConstant<uint32_t>(1, int_type_->IsSigned());
|
||||
// Create the increment.
|
||||
// Note that we do "1 + 1" here, one of the operand should the phi
|
||||
// value but we don't have it yet. The operand will be set latter.
|
||||
@ -162,8 +162,7 @@ void LoopPeeling::InsertCanonicalInductionVariable(
|
||||
|
||||
canonical_induction_variable_ = builder.AddPhi(
|
||||
uint_1_cst->type_id(),
|
||||
{builder.Add32BitConstantInteger<uint32_t>(0, int_type_->IsSigned())
|
||||
->result_id(),
|
||||
{builder.GetIntConstant<uint32_t>(0, int_type_->IsSigned())->result_id(),
|
||||
GetClonedLoop()->GetPreHeaderBlock()->id(), iv_inc->result_id(),
|
||||
GetClonedLoop()->GetLatchBlock()->id()});
|
||||
// Connect everything.
|
||||
@ -422,7 +421,7 @@ void LoopPeeling::PeelBefore(uint32_t peel_factor) {
|
||||
context_, &*cloned_loop_->GetPreHeaderBlock()->tail(),
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
Instruction* factor =
|
||||
builder.Add32BitConstantInteger(peel_factor, int_type_->IsSigned());
|
||||
builder.GetIntConstant(peel_factor, int_type_->IsSigned());
|
||||
|
||||
Instruction* has_remaining_iteration = builder.AddLessThan(
|
||||
factor->result_id(), loop_iteration_count_->result_id());
|
||||
@ -484,7 +483,7 @@ void LoopPeeling::PeelAfter(uint32_t peel_factor) {
|
||||
context_, &*cloned_loop_->GetPreHeaderBlock()->tail(),
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
Instruction* factor =
|
||||
builder.Add32BitConstantInteger(peel_factor, int_type_->IsSigned());
|
||||
builder.GetIntConstant(peel_factor, int_type_->IsSigned());
|
||||
|
||||
Instruction* has_remaining_iteration = builder.AddLessThan(
|
||||
factor->result_id(), loop_iteration_count_->result_id());
|
||||
@ -677,8 +676,8 @@ std::pair<bool, Loop*> LoopPeelingPass::ProcessLoop(Loop* loop,
|
||||
InstructionBuilder(
|
||||
context(), loop->GetHeaderBlock(),
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping)
|
||||
.Add32BitConstantInteger<uint32_t>(static_cast<uint32_t>(iterations),
|
||||
is_signed),
|
||||
.GetIntConstant<uint32_t>(static_cast<uint32_t>(iterations),
|
||||
is_signed),
|
||||
canonical_induction_variable);
|
||||
|
||||
if (!peeler.CanPeelLoop()) {
|
||||
|
@ -452,11 +452,9 @@ void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop,
|
||||
// If the remainder is negative then we add a signed constant, otherwise just
|
||||
// add an unsigned constant.
|
||||
if (remainder < 0) {
|
||||
new_constant =
|
||||
builder.Add32BitSignedIntegerConstant(static_cast<int32_t>(remainder));
|
||||
new_constant = builder.GetSintConstant(static_cast<int32_t>(remainder));
|
||||
} else {
|
||||
new_constant = builder.Add32BitUnsignedIntegerConstant(
|
||||
static_cast<int32_t>(remainder));
|
||||
new_constant = builder.GetUintConstant(static_cast<int32_t>(remainder));
|
||||
}
|
||||
|
||||
uint32_t constant_id = new_constant->result_id();
|
||||
|
@ -368,6 +368,12 @@ 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") {
|
||||
RegisterPass(CreateInstBindlessCheckPass(7, 23));
|
||||
RegisterPass(CreateSimplificationPass());
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
RegisterPass(CreateBlockMergePass());
|
||||
RegisterPass(CreateAggressiveDCEPass());
|
||||
} else if (pass_name == "simplify-instructions") {
|
||||
RegisterPass(CreateSimplificationPass());
|
||||
} else if (pass_name == "ssa-rewrite") {
|
||||
@ -747,4 +753,11 @@ Optimizer::PassToken CreateCombineAccessChainsPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::CombineAccessChains>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
|
||||
uint32_t shader_id) {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
|
||||
}
|
||||
|
||||
} // namespace spvtools
|
||||
|
@ -44,8 +44,9 @@ bool Pass::ProcessEntryPointCallTree(ProcessFunction& pfn, Module* module) {
|
||||
|
||||
// Collect all of the entry points as the roots.
|
||||
std::queue<uint32_t> roots;
|
||||
for (auto& e : module->entry_points())
|
||||
for (auto& e : module->entry_points()) {
|
||||
roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
|
||||
}
|
||||
return ProcessCallTreeFromRoots(pfn, id2function, &roots);
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
#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/licm_pass.h"
|
||||
#include "source/opt/local_access_chain_convert_pass.h"
|
||||
#include "source/opt/local_redundancy_elimination.h"
|
||||
|
@ -555,6 +555,14 @@ Function::Function(Type* ret_type, const std::vector<const Type*>& params)
|
||||
}
|
||||
}
|
||||
|
||||
Function::Function(Type* ret_type, std::vector<const Type*>& params)
|
||||
: Type(kFunction), return_type_(ret_type), param_types_(params) {
|
||||
for (auto* t : params) {
|
||||
(void)t;
|
||||
assert(!t->AsVoid());
|
||||
}
|
||||
}
|
||||
|
||||
bool Function::IsSameImpl(const Type* that, IsSameCache* seen) const {
|
||||
const Function* ft = that->AsFunction();
|
||||
if (!ft) return false;
|
||||
|
@ -491,6 +491,7 @@ class Pointer : public Type {
|
||||
class Function : public Type {
|
||||
public:
|
||||
Function(Type* ret_type, const std::vector<const Type*>& params);
|
||||
Function(Type* ret_type, std::vector<const Type*>& params);
|
||||
Function(const Function&) = default;
|
||||
|
||||
std::string str() const override;
|
||||
|
@ -42,6 +42,7 @@ add_spvtools_unittest(TARGET opt
|
||||
inline_opaque_test.cpp
|
||||
inline_test.cpp
|
||||
insert_extract_elim_test.cpp
|
||||
inst_bindless_check_test.cpp
|
||||
instruction_list_test.cpp
|
||||
instruction_test.cpp
|
||||
ir_builder.cpp
|
||||
@ -86,4 +87,3 @@ add_spvtools_unittest(TARGET opt
|
||||
LIBS SPIRV-Tools-opt
|
||||
PCH_FILE pch_test_opt
|
||||
)
|
||||
|
||||
|
1850
test/opt/inst_bindless_check_test.cpp
Normal file
1850
test/opt/inst_bindless_check_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -317,20 +317,20 @@ OpFunctionEnd
|
||||
|
||||
InstructionBuilder builder(context.get(),
|
||||
&*context->module()->begin()->begin()->begin());
|
||||
EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(13));
|
||||
EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(-1));
|
||||
EXPECT_NE(nullptr, builder.GetUintConstant(13));
|
||||
EXPECT_NE(nullptr, builder.GetSintConstant(-1));
|
||||
|
||||
// Try adding the same constants again to make sure they aren't added.
|
||||
EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(13));
|
||||
EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(-1));
|
||||
EXPECT_NE(nullptr, builder.GetUintConstant(13));
|
||||
EXPECT_NE(nullptr, builder.GetSintConstant(-1));
|
||||
|
||||
// Try adding different constants to make sure the type is reused.
|
||||
EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(1));
|
||||
EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(34));
|
||||
EXPECT_NE(nullptr, builder.GetUintConstant(1));
|
||||
EXPECT_NE(nullptr, builder.GetSintConstant(34));
|
||||
|
||||
// Try adding 0 as both signed and unsigned.
|
||||
EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(0));
|
||||
EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(0));
|
||||
EXPECT_NE(nullptr, builder.GetUintConstant(0));
|
||||
EXPECT_NE(nullptr, builder.GetSintConstant(0));
|
||||
|
||||
Match(text, context.get());
|
||||
}
|
||||
@ -362,25 +362,25 @@ OpFunctionEnd
|
||||
|
||||
InstructionBuilder builder(context.get(),
|
||||
&*context->module()->begin()->begin()->begin());
|
||||
Instruction* const_1 = builder.Add32BitUnsignedIntegerConstant(13);
|
||||
Instruction* const_2 = builder.Add32BitSignedIntegerConstant(-1);
|
||||
Instruction* const_1 = builder.GetUintConstant(13);
|
||||
Instruction* const_2 = builder.GetSintConstant(-1);
|
||||
|
||||
EXPECT_NE(nullptr, const_1);
|
||||
EXPECT_NE(nullptr, const_2);
|
||||
|
||||
// Try adding the same constants again to make sure they aren't added.
|
||||
EXPECT_EQ(const_1, builder.Add32BitUnsignedIntegerConstant(13));
|
||||
EXPECT_EQ(const_2, builder.Add32BitSignedIntegerConstant(-1));
|
||||
EXPECT_EQ(const_1, builder.GetUintConstant(13));
|
||||
EXPECT_EQ(const_2, builder.GetSintConstant(-1));
|
||||
|
||||
Instruction* const_3 = builder.Add32BitUnsignedIntegerConstant(1);
|
||||
Instruction* const_4 = builder.Add32BitSignedIntegerConstant(34);
|
||||
Instruction* const_3 = builder.GetUintConstant(1);
|
||||
Instruction* const_4 = builder.GetSintConstant(34);
|
||||
|
||||
// Try adding different constants to make sure the type is reused.
|
||||
EXPECT_NE(nullptr, const_3);
|
||||
EXPECT_NE(nullptr, const_4);
|
||||
|
||||
Instruction* const_5 = builder.Add32BitUnsignedIntegerConstant(0);
|
||||
Instruction* const_6 = builder.Add32BitSignedIntegerConstant(0);
|
||||
Instruction* const_5 = builder.GetUintConstant(0);
|
||||
Instruction* const_6 = builder.GetSintConstant(0);
|
||||
|
||||
// Try adding 0 as both signed and unsigned.
|
||||
EXPECT_NE(nullptr, const_5);
|
||||
|
@ -132,7 +132,7 @@ TEST_F(PeelingTest, CannotPeel) {
|
||||
} else {
|
||||
InstructionBuilder builder(context.get(), &*f.begin());
|
||||
// Exit condition.
|
||||
loop_count = builder.Add32BitSignedIntegerConstant(10);
|
||||
loop_count = builder.GetSintConstant(10);
|
||||
}
|
||||
|
||||
LoopPeeling peel(&*ld.begin(), loop_count);
|
||||
@ -494,7 +494,7 @@ TEST_F(PeelingTest, SimplePeeling) {
|
||||
|
||||
InstructionBuilder builder(context.get(), &*f.begin());
|
||||
// Exit condition.
|
||||
Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
|
||||
Instruction* ten_cst = builder.GetSintConstant(10);
|
||||
|
||||
LoopPeeling peel(&*ld.begin(), ten_cst);
|
||||
EXPECT_TRUE(peel.CanPeelLoop());
|
||||
@ -548,7 +548,7 @@ CHECK-NEXT: OpLoopMerge
|
||||
|
||||
InstructionBuilder builder(context.get(), &*f.begin());
|
||||
// Exit condition.
|
||||
Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
|
||||
Instruction* ten_cst = builder.GetSintConstant(10);
|
||||
|
||||
LoopPeeling peel(&*ld.begin(), ten_cst);
|
||||
EXPECT_TRUE(peel.CanPeelLoop());
|
||||
@ -604,7 +604,7 @@ CHECK-NEXT: OpLoopMerge
|
||||
|
||||
InstructionBuilder builder(context.get(), &*f.begin());
|
||||
// Exit condition.
|
||||
Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
|
||||
Instruction* ten_cst = builder.GetSintConstant(10);
|
||||
|
||||
LoopPeeling peel(&*ld.begin(), ten_cst,
|
||||
context->get_def_use_mgr()->GetDef(22));
|
||||
@ -657,7 +657,7 @@ CHECK-NEXT: OpLoopMerge
|
||||
|
||||
InstructionBuilder builder(context.get(), &*f.begin());
|
||||
// Exit condition.
|
||||
Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10);
|
||||
Instruction* ten_cst = builder.GetSintConstant(10);
|
||||
|
||||
LoopPeeling peel(&*ld.begin(), ten_cst,
|
||||
context->get_def_use_mgr()->GetDef(22));
|
||||
@ -918,7 +918,7 @@ TEST_F(PeelingTest, DoWhilePeeling) {
|
||||
EXPECT_EQ(ld.NumLoops(), 1u);
|
||||
InstructionBuilder builder(context.get(), &*f.begin());
|
||||
// Exit condition.
|
||||
Instruction* ten_cst = builder.Add32BitUnsignedIntegerConstant(10);
|
||||
Instruction* ten_cst = builder.GetUintConstant(10);
|
||||
|
||||
LoopPeeling peel(&*ld.begin(), ten_cst);
|
||||
EXPECT_TRUE(peel.CanPeelLoop());
|
||||
@ -968,7 +968,7 @@ CHECK-NEXT: OpLoopMerge
|
||||
|
||||
InstructionBuilder builder(context.get(), &*f.begin());
|
||||
// Exit condition.
|
||||
Instruction* ten_cst = builder.Add32BitUnsignedIntegerConstant(10);
|
||||
Instruction* ten_cst = builder.GetUintConstant(10);
|
||||
|
||||
LoopPeeling peel(&*ld.begin(), ten_cst);
|
||||
EXPECT_TRUE(peel.CanPeelLoop());
|
||||
|
Loading…
Reference in New Issue
Block a user