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:
greg-lunarg 2018-11-08 11:54:54 -07:00 committed by Steven Perron
parent 6721478ef1
commit 1e9fc1aac1
28 changed files with 3795 additions and 52 deletions

View File

@ -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 \

View File

@ -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",

View File

@ -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)

View 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_

View File

@ -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_

View File

@ -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

View File

@ -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(); }

View File

@ -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) {

View File

@ -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.

View File

@ -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;

View 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

View 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_

View 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

View 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_

View File

@ -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)

View File

@ -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)) {

View File

@ -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) {

View File

@ -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()) {

View File

@ -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();

View File

@ -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

View File

@ -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);
}

View File

@ -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"

View File

@ -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;

View File

@ -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;

View File

@ -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
)

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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());