mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-24 20:40:13 +00:00
opt: Remove InstDebugPrintfPass (#5856)
This code was only used by Vulkan-Validation layers and it has now been replaced by code in that repository.
This commit is contained in:
parent
14388d7014
commit
ba37b3b513
@ -129,10 +129,8 @@ SPVTOOLS_OPT_SRC_FILES := \
|
||||
source/opt/inline_pass.cpp \
|
||||
source/opt/inline_exhaustive_pass.cpp \
|
||||
source/opt/inline_opaque_pass.cpp \
|
||||
source/opt/inst_debug_printf_pass.cpp \
|
||||
source/opt/instruction.cpp \
|
||||
source/opt/instruction_list.cpp \
|
||||
source/opt/instrument_pass.cpp \
|
||||
source/opt/interface_var_sroa.cpp \
|
||||
source/opt/interp_fixup_pass.cpp \
|
||||
source/opt/invocation_interlock_placement_pass.cpp \
|
||||
|
@ -178,7 +178,6 @@ cc_library(
|
||||
cc_library(
|
||||
name = "spirv_tools_opt",
|
||||
hdrs = [
|
||||
"include/spirv-tools/instrument.hpp",
|
||||
"include/spirv-tools/optimizer.hpp",
|
||||
],
|
||||
copts = COMMON_COPTS,
|
||||
@ -196,7 +195,6 @@ cc_library(
|
||||
":gen_vendor_tables_spv_amd_shader_ballot",
|
||||
],
|
||||
hdrs = glob(["source/opt/*.h"]) + [
|
||||
"include/spirv-tools/instrument.hpp",
|
||||
"include/spirv-tools/optimizer.hpp",
|
||||
],
|
||||
copts = COMMON_COPTS,
|
||||
|
5
BUILD.gn
5
BUILD.gn
@ -388,7 +388,6 @@ config("spvtools_internal_config") {
|
||||
|
||||
source_set("spvtools_headers") {
|
||||
sources = [
|
||||
"include/spirv-tools/instrument.hpp",
|
||||
"include/spirv-tools/libspirv.h",
|
||||
"include/spirv-tools/libspirv.hpp",
|
||||
"include/spirv-tools/linker.hpp",
|
||||
@ -683,14 +682,10 @@ static_library("spvtools_opt") {
|
||||
"source/opt/inline_opaque_pass.h",
|
||||
"source/opt/inline_pass.cpp",
|
||||
"source/opt/inline_pass.h",
|
||||
"source/opt/inst_debug_printf_pass.cpp",
|
||||
"source/opt/inst_debug_printf_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/interface_var_sroa.cpp",
|
||||
"source/opt/interface_var_sroa.h",
|
||||
"source/opt/interp_fixup_pass.cpp",
|
||||
|
@ -371,7 +371,6 @@ 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)
|
||||
|
@ -1,79 +0,0 @@
|
||||
// Copyright (c) 2018 The Khronos Group Inc.
|
||||
// Copyright (c) 2018 Valve Corporation
|
||||
// Copyright (c) 2018 LunarG Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef 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:
|
||||
//
|
||||
// CreateInstDebugPrintfPass
|
||||
//
|
||||
// More detailed documentation of these routines can be found in optimizer.hpp
|
||||
|
||||
namespace spvtools {
|
||||
|
||||
// Stream Output Buffer Offsets
|
||||
//
|
||||
// The following values provide offsets into the output buffer struct
|
||||
// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
|
||||
// by InstDebugPrintfPass.
|
||||
//
|
||||
// The 1st member of the debug output buffer contains a set of flags
|
||||
// controlling the behavior of instrumentation code.
|
||||
static const int kDebugOutputFlagsOffset = 0;
|
||||
|
||||
// The 2nd member of the debug output buffer contains the next available word
|
||||
// in the data stream to be written. Shaders will atomically read and update
|
||||
// this value so as not to overwrite each others records. This value must be
|
||||
// initialized to zero
|
||||
static const int kDebugOutputSizeOffset = 1;
|
||||
|
||||
// The 3rd member 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 = 2;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 InstDebugPrintfPass.
|
||||
static const int kDebugOutputPrintfStream = 3;
|
||||
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
|
@ -747,18 +747,6 @@ Optimizer::PassToken CreateReduceLoadSizePass(
|
||||
// them into a single instruction where possible.
|
||||
Optimizer::PassToken CreateCombineAccessChainsPass();
|
||||
|
||||
// Create a pass to instrument OpDebugPrintf instructions.
|
||||
// This pass replaces all OpDebugPrintf instructions with instructions to write
|
||||
// a record containing the string id and the all specified values into a special
|
||||
// printf output buffer (if space allows). This pass is designed to support
|
||||
// the printf validation in the Vulkan validation layers.
|
||||
//
|
||||
// The instrumentation will write buffers in debug descriptor set |desc_set|.
|
||||
// It will write |shader_id| in each output record to identify the shader
|
||||
// module which generated the record.
|
||||
Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
|
||||
uint32_t shader_id);
|
||||
|
||||
// Create a pass to upgrade to the VulkanKHR memory model.
|
||||
// This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
|
||||
// Additionally, it modifies memory, image, atomic and barrier operations to
|
||||
|
@ -64,10 +64,8 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
inline_exhaustive_pass.h
|
||||
inline_opaque_pass.h
|
||||
inline_pass.h
|
||||
inst_debug_printf_pass.h
|
||||
instruction.h
|
||||
instruction_list.h
|
||||
instrument_pass.h
|
||||
interface_var_sroa.h
|
||||
invocation_interlock_placement_pass.h
|
||||
interp_fixup_pass.h
|
||||
@ -185,10 +183,8 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
inline_exhaustive_pass.cpp
|
||||
inline_opaque_pass.cpp
|
||||
inline_pass.cpp
|
||||
inst_debug_printf_pass.cpp
|
||||
instruction.cpp
|
||||
instruction_list.cpp
|
||||
instrument_pass.cpp
|
||||
interface_var_sroa.cpp
|
||||
invocation_interlock_placement_pass.cpp
|
||||
interp_fixup_pass.cpp
|
||||
|
@ -1,484 +0,0 @@
|
||||
// Copyright (c) 2020 The Khronos Group Inc.
|
||||
// Copyright (c) 2020 Valve Corporation
|
||||
// Copyright (c) 2020 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_debug_printf_pass.h"
|
||||
|
||||
#include "source/spirv_constant.h"
|
||||
#include "source/to_string.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "spirv/unified1/NonSemanticDebugPrintf.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
void InstDebugPrintfPass::GenOutputValues(Instruction* val_inst,
|
||||
std::vector<uint32_t>* val_ids,
|
||||
InstructionBuilder* builder) {
|
||||
uint32_t val_ty_id = val_inst->type_id();
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Type* val_ty = type_mgr->GetType(val_ty_id);
|
||||
switch (val_ty->kind()) {
|
||||
case analysis::Type::kVector: {
|
||||
analysis::Vector* v_ty = val_ty->AsVector();
|
||||
const analysis::Type* c_ty = v_ty->element_type();
|
||||
uint32_t c_ty_id = type_mgr->GetId(c_ty);
|
||||
for (uint32_t c = 0; c < v_ty->element_count(); ++c) {
|
||||
Instruction* c_inst =
|
||||
builder->AddCompositeExtract(c_ty_id, val_inst->result_id(), {c});
|
||||
GenOutputValues(c_inst, val_ids, builder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case analysis::Type::kBool: {
|
||||
// Select between uint32 zero or one
|
||||
uint32_t zero_id = builder->GetUintConstantId(0);
|
||||
uint32_t one_id = builder->GetUintConstantId(1);
|
||||
Instruction* sel_inst = builder->AddSelect(
|
||||
GetUintId(), val_inst->result_id(), one_id, zero_id);
|
||||
val_ids->push_back(sel_inst->result_id());
|
||||
return;
|
||||
}
|
||||
case analysis::Type::kFloat: {
|
||||
analysis::Float* f_ty = val_ty->AsFloat();
|
||||
switch (f_ty->width()) {
|
||||
case 16: {
|
||||
// Convert float16 to float32 and recurse
|
||||
Instruction* f32_inst = builder->AddUnaryOp(
|
||||
GetFloatId(), spv::Op::OpFConvert, val_inst->result_id());
|
||||
GenOutputValues(f32_inst, val_ids, builder);
|
||||
return;
|
||||
}
|
||||
case 64: {
|
||||
// Bitcast float64 to uint64 and recurse
|
||||
Instruction* ui64_inst = builder->AddUnaryOp(
|
||||
GetUint64Id(), spv::Op::OpBitcast, val_inst->result_id());
|
||||
GenOutputValues(ui64_inst, val_ids, builder);
|
||||
return;
|
||||
}
|
||||
case 32: {
|
||||
// Bitcase float32 to uint32
|
||||
Instruction* bc_inst = builder->AddUnaryOp(
|
||||
GetUintId(), spv::Op::OpBitcast, val_inst->result_id());
|
||||
val_ids->push_back(bc_inst->result_id());
|
||||
return;
|
||||
}
|
||||
default:
|
||||
assert(false && "unsupported float width");
|
||||
return;
|
||||
}
|
||||
}
|
||||
case analysis::Type::kInteger: {
|
||||
analysis::Integer* i_ty = val_ty->AsInteger();
|
||||
switch (i_ty->width()) {
|
||||
case 64: {
|
||||
Instruction* ui64_inst = val_inst;
|
||||
if (i_ty->IsSigned()) {
|
||||
// Bitcast sint64 to uint64
|
||||
ui64_inst = builder->AddUnaryOp(GetUint64Id(), spv::Op::OpBitcast,
|
||||
val_inst->result_id());
|
||||
}
|
||||
// Break uint64 into 2x uint32
|
||||
Instruction* lo_ui64_inst = builder->AddUnaryOp(
|
||||
GetUintId(), spv::Op::OpUConvert, ui64_inst->result_id());
|
||||
Instruction* rshift_ui64_inst = builder->AddBinaryOp(
|
||||
GetUint64Id(), spv::Op::OpShiftRightLogical,
|
||||
ui64_inst->result_id(), builder->GetUintConstantId(32));
|
||||
Instruction* hi_ui64_inst = builder->AddUnaryOp(
|
||||
GetUintId(), spv::Op::OpUConvert, rshift_ui64_inst->result_id());
|
||||
val_ids->push_back(lo_ui64_inst->result_id());
|
||||
val_ids->push_back(hi_ui64_inst->result_id());
|
||||
return;
|
||||
}
|
||||
case 8: {
|
||||
Instruction* ui8_inst = val_inst;
|
||||
if (i_ty->IsSigned()) {
|
||||
// Bitcast sint8 to uint8
|
||||
ui8_inst = builder->AddUnaryOp(GetUint8Id(), spv::Op::OpBitcast,
|
||||
val_inst->result_id());
|
||||
}
|
||||
// Convert uint8 to uint32
|
||||
Instruction* ui32_inst = builder->AddUnaryOp(
|
||||
GetUintId(), spv::Op::OpUConvert, ui8_inst->result_id());
|
||||
val_ids->push_back(ui32_inst->result_id());
|
||||
return;
|
||||
}
|
||||
case 32: {
|
||||
Instruction* ui32_inst = val_inst;
|
||||
if (i_ty->IsSigned()) {
|
||||
// Bitcast sint32 to uint32
|
||||
ui32_inst = builder->AddUnaryOp(GetUintId(), spv::Op::OpBitcast,
|
||||
val_inst->result_id());
|
||||
}
|
||||
// uint32 needs no further processing
|
||||
val_ids->push_back(ui32_inst->result_id());
|
||||
return;
|
||||
}
|
||||
default:
|
||||
// TODO(greg-lunarg): Support non-32-bit int
|
||||
assert(false && "unsupported int width");
|
||||
return;
|
||||
}
|
||||
}
|
||||
default:
|
||||
assert(false && "unsupported type");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void InstDebugPrintfPass::GenOutputCode(
|
||||
Instruction* printf_inst,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
BasicBlock* back_blk_ptr = &*new_blocks->back();
|
||||
InstructionBuilder builder(
|
||||
context(), back_blk_ptr,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
// Gen debug printf record validation-specific values. The format string
|
||||
// will have its id written. Vectors will need to be broken down into
|
||||
// component values. float16 will need to be converted to float32. Pointer
|
||||
// and uint64 will need to be converted to two uint32 values. float32 will
|
||||
// need to be bitcast to uint32. int32 will need to be bitcast to uint32.
|
||||
std::vector<uint32_t> val_ids;
|
||||
bool is_first_operand = false;
|
||||
printf_inst->ForEachInId(
|
||||
[&is_first_operand, &val_ids, &builder, this](const uint32_t* iid) {
|
||||
// skip set operand
|
||||
if (!is_first_operand) {
|
||||
is_first_operand = true;
|
||||
return;
|
||||
}
|
||||
Instruction* opnd_inst = get_def_use_mgr()->GetDef(*iid);
|
||||
if (opnd_inst->opcode() == spv::Op::OpString) {
|
||||
uint32_t string_id_id = builder.GetUintConstantId(*iid);
|
||||
val_ids.push_back(string_id_id);
|
||||
} else {
|
||||
GenOutputValues(opnd_inst, &val_ids, &builder);
|
||||
}
|
||||
});
|
||||
GenDebugStreamWrite(
|
||||
builder.GetUintConstantId(shader_id_),
|
||||
builder.GetUintConstantId(uid2offset_[printf_inst->unique_id()]), val_ids,
|
||||
&builder);
|
||||
context()->KillInst(printf_inst);
|
||||
}
|
||||
|
||||
void InstDebugPrintfPass::GenDebugPrintfCode(
|
||||
BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
// If not DebugPrintf OpExtInst, return.
|
||||
Instruction* printf_inst = &*ref_inst_itr;
|
||||
if (printf_inst->opcode() != spv::Op::OpExtInst) return;
|
||||
if (printf_inst->GetSingleWordInOperand(0) != ext_inst_printf_id_) return;
|
||||
if (printf_inst->GetSingleWordInOperand(1) !=
|
||||
NonSemanticDebugPrintfDebugPrintf)
|
||||
return;
|
||||
// Initialize DefUse manager before dismantling module
|
||||
(void)get_def_use_mgr();
|
||||
// Move original block's preceding instructions into first new block
|
||||
std::unique_ptr<BasicBlock> new_blk_ptr;
|
||||
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
|
||||
new_blocks->push_back(std::move(new_blk_ptr));
|
||||
// Generate instructions to output printf args to printf buffer
|
||||
GenOutputCode(printf_inst, new_blocks);
|
||||
// Caller expects at least two blocks with last block containing remaining
|
||||
// code, so end block after instrumentation, create remainder block, and
|
||||
// branch to it
|
||||
uint32_t rem_blk_id = TakeNextId();
|
||||
std::unique_ptr<Instruction> rem_label(NewLabel(rem_blk_id));
|
||||
BasicBlock* back_blk_ptr = &*new_blocks->back();
|
||||
InstructionBuilder builder(
|
||||
context(), back_blk_ptr,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
(void)builder.AddBranch(rem_blk_id);
|
||||
// Gen remainder block
|
||||
new_blk_ptr.reset(new BasicBlock(std::move(rem_label)));
|
||||
builder.SetInsertPoint(&*new_blk_ptr);
|
||||
// Move original block's remaining code into remainder block and add
|
||||
// to new blocks
|
||||
MovePostludeCode(ref_block_itr, &*new_blk_ptr);
|
||||
new_blocks->push_back(std::move(new_blk_ptr));
|
||||
}
|
||||
|
||||
// Return id for output buffer
|
||||
uint32_t InstDebugPrintfPass::GetOutputBufferId() {
|
||||
if (output_buffer_id_ == 0) {
|
||||
// If not created yet, create one
|
||||
analysis::DecorationManager* deco_mgr = get_decoration_mgr();
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::RuntimeArray* reg_uint_rarr_ty = GetUintRuntimeArrayType(32);
|
||||
analysis::Integer* reg_uint_ty = GetInteger(32, false);
|
||||
analysis::Type* reg_buf_ty =
|
||||
GetStruct({reg_uint_ty, reg_uint_ty, reg_uint_rarr_ty});
|
||||
uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
|
||||
// By the Vulkan spec, a pre-existing struct containing a RuntimeArray
|
||||
// must be a block, and will therefore be decorated with Block. Therefore
|
||||
// the undecorated type returned here will not be pre-existing and can
|
||||
// safely be decorated. Since this type is now decorated, it is out of
|
||||
// sync with the TypeManager and therefore the TypeManager must be
|
||||
// invalidated after this pass.
|
||||
assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
|
||||
"used struct type returned");
|
||||
deco_mgr->AddDecoration(obufTyId, uint32_t(spv::Decoration::Block));
|
||||
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputFlagsOffset,
|
||||
uint32_t(spv::Decoration::Offset), 0);
|
||||
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
|
||||
uint32_t(spv::Decoration::Offset), 4);
|
||||
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputDataOffset,
|
||||
uint32_t(spv::Decoration::Offset), 8);
|
||||
uint32_t obufTyPtrId_ =
|
||||
type_mgr->FindPointerToType(obufTyId, spv::StorageClass::StorageBuffer);
|
||||
output_buffer_id_ = TakeNextId();
|
||||
std::unique_ptr<Instruction> newVarOp(new Instruction(
|
||||
context(), spv::Op::OpVariable, obufTyPtrId_, output_buffer_id_,
|
||||
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||
{uint32_t(spv::StorageClass::StorageBuffer)}}}));
|
||||
context()->AddGlobalValue(std::move(newVarOp));
|
||||
context()->AddDebug2Inst(NewGlobalName(obufTyId, "OutputBuffer"));
|
||||
context()->AddDebug2Inst(NewMemberName(obufTyId, 0, "flags"));
|
||||
context()->AddDebug2Inst(NewMemberName(obufTyId, 1, "written_count"));
|
||||
context()->AddDebug2Inst(NewMemberName(obufTyId, 2, "data"));
|
||||
context()->AddDebug2Inst(NewGlobalName(output_buffer_id_, "output_buffer"));
|
||||
deco_mgr->AddDecorationVal(
|
||||
output_buffer_id_, uint32_t(spv::Decoration::DescriptorSet), desc_set_);
|
||||
deco_mgr->AddDecorationVal(output_buffer_id_,
|
||||
uint32_t(spv::Decoration::Binding),
|
||||
GetOutputBufferBinding());
|
||||
AddStorageBufferExt();
|
||||
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
|
||||
// Add the new buffer to all entry points.
|
||||
for (auto& entry : get_module()->entry_points()) {
|
||||
entry.AddOperand({SPV_OPERAND_TYPE_ID, {output_buffer_id_}});
|
||||
context()->AnalyzeUses(&entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
return output_buffer_id_;
|
||||
}
|
||||
|
||||
uint32_t InstDebugPrintfPass::GetOutputBufferPtrId() {
|
||||
if (output_buffer_ptr_id_ == 0) {
|
||||
output_buffer_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
|
||||
GetUintId(), spv::StorageClass::StorageBuffer);
|
||||
}
|
||||
return output_buffer_ptr_id_;
|
||||
}
|
||||
|
||||
uint32_t InstDebugPrintfPass::GetOutputBufferBinding() {
|
||||
return kDebugOutputPrintfStream;
|
||||
}
|
||||
|
||||
void InstDebugPrintfPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
|
||||
uint32_t field_offset,
|
||||
uint32_t field_value_id,
|
||||
InstructionBuilder* builder) {
|
||||
// Cast value to 32-bit unsigned if necessary
|
||||
uint32_t val_id = GenUintCastCode(field_value_id, builder);
|
||||
// Store value
|
||||
Instruction* data_idx_inst = builder->AddIAdd(
|
||||
GetUintId(), base_offset_id, builder->GetUintConstantId(field_offset));
|
||||
uint32_t buf_id = GetOutputBufferId();
|
||||
uint32_t buf_uint_ptr_id = GetOutputBufferPtrId();
|
||||
Instruction* achain_inst = builder->AddAccessChain(
|
||||
buf_uint_ptr_id, buf_id,
|
||||
{builder->GetUintConstantId(kDebugOutputDataOffset),
|
||||
data_idx_inst->result_id()});
|
||||
(void)builder->AddStore(achain_inst->result_id(), val_id);
|
||||
}
|
||||
|
||||
uint32_t InstDebugPrintfPass::GetStreamWriteFunctionId(uint32_t param_cnt) {
|
||||
enum {
|
||||
kShaderId = 0,
|
||||
kInstructionIndex = 1,
|
||||
kFirstParam = 2,
|
||||
};
|
||||
// Total param count is common params plus validation-specific
|
||||
// params
|
||||
if (param2output_func_id_[param_cnt] == 0) {
|
||||
// Create function
|
||||
param2output_func_id_[param_cnt] = TakeNextId();
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
|
||||
const analysis::Type* uint_type = GetInteger(32, false);
|
||||
|
||||
std::vector<const analysis::Type*> param_types(kFirstParam + param_cnt,
|
||||
uint_type);
|
||||
std::unique_ptr<Function> output_func = StartFunction(
|
||||
param2output_func_id_[param_cnt], type_mgr->GetVoidType(), param_types);
|
||||
|
||||
std::vector<uint32_t> param_ids = AddParameters(*output_func, param_types);
|
||||
|
||||
// Create first block
|
||||
auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
|
||||
|
||||
InstructionBuilder builder(
|
||||
context(), &*new_blk_ptr,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
// Gen test if debug output buffer size will not be exceeded.
|
||||
const uint32_t first_param_offset = kInstCommonOutInstructionIdx + 1;
|
||||
const uint32_t obuf_record_sz = first_param_offset + param_cnt;
|
||||
const uint32_t buf_id = GetOutputBufferId();
|
||||
const uint32_t buf_uint_ptr_id = GetOutputBufferPtrId();
|
||||
Instruction* obuf_curr_sz_ac_inst = builder.AddAccessChain(
|
||||
buf_uint_ptr_id, buf_id,
|
||||
{builder.GetUintConstantId(kDebugOutputSizeOffset)});
|
||||
// Fetch the current debug buffer written size atomically, adding the
|
||||
// size of the record to be written.
|
||||
uint32_t obuf_record_sz_id = builder.GetUintConstantId(obuf_record_sz);
|
||||
uint32_t mask_none_id =
|
||||
builder.GetUintConstantId(uint32_t(spv::MemoryAccessMask::MaskNone));
|
||||
uint32_t scope_invok_id =
|
||||
builder.GetUintConstantId(uint32_t(spv::Scope::Invocation));
|
||||
Instruction* obuf_curr_sz_inst = builder.AddQuadOp(
|
||||
GetUintId(), spv::Op::OpAtomicIAdd, obuf_curr_sz_ac_inst->result_id(),
|
||||
scope_invok_id, mask_none_id, obuf_record_sz_id);
|
||||
uint32_t obuf_curr_sz_id = obuf_curr_sz_inst->result_id();
|
||||
// Compute new written size
|
||||
Instruction* obuf_new_sz_inst =
|
||||
builder.AddIAdd(GetUintId(), obuf_curr_sz_id,
|
||||
builder.GetUintConstantId(obuf_record_sz));
|
||||
// Fetch the data bound
|
||||
Instruction* obuf_bnd_inst =
|
||||
builder.AddIdLiteralOp(GetUintId(), spv::Op::OpArrayLength,
|
||||
GetOutputBufferId(), kDebugOutputDataOffset);
|
||||
// Test that new written size is less than or equal to debug output
|
||||
// data bound
|
||||
Instruction* obuf_safe_inst = builder.AddBinaryOp(
|
||||
GetBoolId(), spv::Op::OpULessThanEqual, obuf_new_sz_inst->result_id(),
|
||||
obuf_bnd_inst->result_id());
|
||||
uint32_t merge_blk_id = TakeNextId();
|
||||
uint32_t write_blk_id = TakeNextId();
|
||||
std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
|
||||
std::unique_ptr<Instruction> write_label(NewLabel(write_blk_id));
|
||||
(void)builder.AddConditionalBranch(
|
||||
obuf_safe_inst->result_id(), write_blk_id, merge_blk_id, merge_blk_id,
|
||||
uint32_t(spv::SelectionControlMask::MaskNone));
|
||||
// Close safety test block and gen write block
|
||||
output_func->AddBasicBlock(std::move(new_blk_ptr));
|
||||
new_blk_ptr = MakeUnique<BasicBlock>(std::move(write_label));
|
||||
builder.SetInsertPoint(&*new_blk_ptr);
|
||||
// Generate common and stage-specific debug record members
|
||||
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutSize,
|
||||
builder.GetUintConstantId(obuf_record_sz),
|
||||
&builder);
|
||||
// Store Shader Id
|
||||
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutShaderId,
|
||||
param_ids[kShaderId], &builder);
|
||||
// Store Instruction Idx
|
||||
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutInstructionIdx,
|
||||
param_ids[kInstructionIndex], &builder);
|
||||
// Gen writes of validation specific data
|
||||
for (uint32_t i = 0; i < param_cnt; ++i) {
|
||||
GenDebugOutputFieldCode(obuf_curr_sz_id, first_param_offset + i,
|
||||
param_ids[kFirstParam + i], &builder);
|
||||
}
|
||||
// Close write block and gen merge block
|
||||
(void)builder.AddBranch(merge_blk_id);
|
||||
output_func->AddBasicBlock(std::move(new_blk_ptr));
|
||||
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
||||
builder.SetInsertPoint(&*new_blk_ptr);
|
||||
// Close merge block and function and add function to module
|
||||
(void)builder.AddNullaryOp(0, spv::Op::OpReturn);
|
||||
|
||||
output_func->AddBasicBlock(std::move(new_blk_ptr));
|
||||
output_func->SetFunctionEnd(EndFunction());
|
||||
context()->AddFunction(std::move(output_func));
|
||||
|
||||
std::string name("stream_write_");
|
||||
name += spvtools::to_string(param_cnt);
|
||||
|
||||
context()->AddDebug2Inst(
|
||||
NewGlobalName(param2output_func_id_[param_cnt], name));
|
||||
}
|
||||
return param2output_func_id_[param_cnt];
|
||||
}
|
||||
|
||||
void InstDebugPrintfPass::GenDebugStreamWrite(
|
||||
uint32_t shader_id, uint32_t instruction_idx_id,
|
||||
const std::vector<uint32_t>& validation_ids, InstructionBuilder* builder) {
|
||||
// Call debug output function. Pass func_idx, instruction_idx and
|
||||
// validation ids as args.
|
||||
uint32_t val_id_cnt = static_cast<uint32_t>(validation_ids.size());
|
||||
std::vector<uint32_t> args = {shader_id, instruction_idx_id};
|
||||
(void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
|
||||
(void)builder->AddFunctionCall(GetVoidId(),
|
||||
GetStreamWriteFunctionId(val_id_cnt), args);
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> InstDebugPrintfPass::NewGlobalName(
|
||||
uint32_t id, const std::string& name_str) {
|
||||
std::string prefixed_name{"inst_printf_"};
|
||||
prefixed_name += name_str;
|
||||
return NewName(id, prefixed_name);
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> InstDebugPrintfPass::NewMemberName(
|
||||
uint32_t id, uint32_t member_index, const std::string& name_str) {
|
||||
return MakeUnique<Instruction>(
|
||||
context(), spv::Op::OpMemberName, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {id}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}});
|
||||
}
|
||||
|
||||
void InstDebugPrintfPass::InitializeInstDebugPrintf() {
|
||||
// Initialize base class
|
||||
InitializeInstrument();
|
||||
output_buffer_id_ = 0;
|
||||
output_buffer_ptr_id_ = 0;
|
||||
}
|
||||
|
||||
Pass::Status InstDebugPrintfPass::ProcessImpl() {
|
||||
// Perform printf instrumentation on each entry point function in module
|
||||
InstProcessFunction pfn =
|
||||
[this](BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
[[maybe_unused]] uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
return GenDebugPrintfCode(ref_inst_itr, ref_block_itr, new_blocks);
|
||||
};
|
||||
(void)InstProcessEntryPointCallTree(pfn);
|
||||
// Remove DebugPrintf OpExtInstImport instruction
|
||||
Instruction* ext_inst_import_inst =
|
||||
get_def_use_mgr()->GetDef(ext_inst_printf_id_);
|
||||
context()->KillInst(ext_inst_import_inst);
|
||||
// If no remaining non-semantic instruction sets, remove non-semantic debug
|
||||
// info extension from module and feature manager
|
||||
bool non_sem_set_seen = false;
|
||||
for (auto c_itr = context()->module()->ext_inst_import_begin();
|
||||
c_itr != context()->module()->ext_inst_import_end(); ++c_itr) {
|
||||
const std::string set_name = c_itr->GetInOperand(0).AsString();
|
||||
if (spvtools::utils::starts_with(set_name, "NonSemantic.")) {
|
||||
non_sem_set_seen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!non_sem_set_seen) {
|
||||
context()->RemoveExtension(kSPV_KHR_non_semantic_info);
|
||||
}
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
Pass::Status InstDebugPrintfPass::Process() {
|
||||
ext_inst_printf_id_ =
|
||||
get_module()->GetExtInstImportId("NonSemantic.DebugPrintf");
|
||||
if (ext_inst_printf_id_ == 0) return Status::SuccessWithoutChange;
|
||||
InitializeInstDebugPrintf();
|
||||
return ProcessImpl();
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
@ -1,198 +0,0 @@
|
||||
// Copyright (c) 2020 The Khronos Group Inc.
|
||||
// Copyright (c) 2020 Valve Corporation
|
||||
// Copyright (c) 2020 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_DEBUG_PRINTF_PASS_H_
|
||||
#define LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
|
||||
|
||||
#include "instrument_pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// This class/pass is designed to support the debug printf GPU-assisted layer
|
||||
// of https://github.com/KhronosGroup/Vulkan-ValidationLayers. Its internal and
|
||||
// external design may change as the layer evolves.
|
||||
class InstDebugPrintfPass : public InstrumentPass {
|
||||
public:
|
||||
// For test harness only
|
||||
InstDebugPrintfPass() : InstrumentPass(7, 23, false, false) {}
|
||||
// For all other interfaces
|
||||
InstDebugPrintfPass(uint32_t desc_set, uint32_t shader_id)
|
||||
: InstrumentPass(desc_set, shader_id, false, false) {}
|
||||
|
||||
~InstDebugPrintfPass() override = default;
|
||||
|
||||
// See optimizer.hpp for pass user documentation.
|
||||
Status Process() override;
|
||||
|
||||
const char* name() const override { return "inst-printf-pass"; }
|
||||
|
||||
private:
|
||||
// Gen code into |builder| to write |field_value_id| into debug output
|
||||
// buffer at |base_offset_id| + |field_offset|.
|
||||
void GenDebugOutputFieldCode(uint32_t base_offset_id, uint32_t field_offset,
|
||||
uint32_t field_value_id,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Generate instructions in |builder| which will atomically fetch and
|
||||
// increment the size of the debug output buffer stream of the current
|
||||
// validation and write a record to the end of the stream, if enough space
|
||||
// in the buffer remains. The record will contain the index of the function
|
||||
// and instruction within that function |func_idx, instruction_idx| which
|
||||
// generated the record. 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
|
||||
// ...
|
||||
// Validation Error Code
|
||||
// Validation-specific Word 0
|
||||
// Validation-specific Word 1
|
||||
// Validation-specific Word 2
|
||||
// ...
|
||||
//
|
||||
// Each record consists of two subsections: members common across all
|
||||
// validation 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 Validation Error Code specifies the exact error which has occurred.
|
||||
// These are enumerated with the kInstError* static consts. This allows
|
||||
// multiple validation layers to use the same, single output buffer.
|
||||
//
|
||||
// The Validation-specific Words are a validation-specific number of 32-bit
|
||||
// words which give further information on the validation error that
|
||||
// occurred. These are documented further in each file containing the
|
||||
// validation-specific class which derives from this base class.
|
||||
//
|
||||
// Because the code that is generated checks against the size of the buffer
|
||||
// before writing, the size of the debug out buffer can be used by the
|
||||
// validation layer to control the number of error records that are written.
|
||||
void GenDebugStreamWrite(uint32_t shader_id, uint32_t instruction_idx_id,
|
||||
const std::vector<uint32_t>& validation_ids,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Return id for output function. Define if it doesn't exist with
|
||||
// |val_spec_param_cnt| validation-specific uint32 parameters.
|
||||
uint32_t GetStreamWriteFunctionId(uint32_t val_spec_param_cnt);
|
||||
|
||||
// Generate instructions for OpDebugPrintf.
|
||||
//
|
||||
// If |ref_inst_itr| is an OpDebugPrintf, return in |new_blocks| the result
|
||||
// of replacing it with buffer write instructions within its block at
|
||||
// |ref_block_itr|. The instructions write a record to the printf
|
||||
// output buffer stream including |function_idx, instruction_idx|
|
||||
// and removes the OpDebugPrintf. The block at |ref_block_itr| can just be
|
||||
// replaced with the block in |new_blocks|. Besides the buffer writes, this
|
||||
// block will comprise all instructions preceding and following
|
||||
// |ref_inst_itr|.
|
||||
//
|
||||
// This function is designed to be passed to
|
||||
// InstrumentPass::InstProcessEntryPointCallTree(), which applies the
|
||||
// function to each instruction in a module and replaces the instruction
|
||||
// if warranted.
|
||||
//
|
||||
// This instrumentation function utilizes GenDebugStreamWrite() to write its
|
||||
// error records. The validation-specific part of the error record will
|
||||
// consist of a uint32 which is the id of the format string plus a sequence
|
||||
// of uint32s representing the values of the remaining operands of the
|
||||
// DebugPrintf.
|
||||
void GenDebugPrintfCode(BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
// Generate a sequence of uint32 instructions in |builder| (if necessary)
|
||||
// representing the value of |val_inst|, which must be a buffer pointer, a
|
||||
// uint64, or a scalar or vector of type uint32, float32 or float16. Append
|
||||
// the ids of all values to the end of |val_ids|.
|
||||
void GenOutputValues(Instruction* val_inst, std::vector<uint32_t>* val_ids,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Generate instructions to write a record containing the operands of
|
||||
// |printf_inst| arguments to printf buffer, adding new code to the end of
|
||||
// the last block in |new_blocks|. Kill OpDebugPrintf instruction.
|
||||
void GenOutputCode(Instruction* printf_inst,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
// Set the name for a function or global variable, names will be
|
||||
// prefixed to identify which instrumentation pass generated them.
|
||||
std::unique_ptr<Instruction> NewGlobalName(uint32_t id,
|
||||
const std::string& name_str);
|
||||
|
||||
// Set the name for a structure member
|
||||
std::unique_ptr<Instruction> NewMemberName(uint32_t id, uint32_t member_index,
|
||||
const std::string& name_str);
|
||||
|
||||
// Return id for debug output buffer
|
||||
uint32_t GetOutputBufferId();
|
||||
|
||||
// Return id for buffer uint type
|
||||
uint32_t GetOutputBufferPtrId();
|
||||
|
||||
// Return binding for output buffer for current validation.
|
||||
uint32_t GetOutputBufferBinding();
|
||||
|
||||
// Initialize state for instrumenting bindless checking
|
||||
void InitializeInstDebugPrintf();
|
||||
|
||||
// Apply GenDebugPrintfCode to every instruction in module.
|
||||
Pass::Status ProcessImpl();
|
||||
|
||||
uint32_t ext_inst_printf_id_{0};
|
||||
|
||||
// id for output buffer variable
|
||||
uint32_t output_buffer_id_{0};
|
||||
|
||||
// ptr type id for output buffer element
|
||||
uint32_t output_buffer_ptr_id_{0};
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
|
@ -1,803 +0,0 @@
|
||||
// Copyright (c) 2018 The Khronos Group Inc.
|
||||
// Copyright (c) 2018 Valve Corporation
|
||||
// Copyright (c) 2018 LunarG Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "instrument_pass.h"
|
||||
|
||||
#include "source/cfa.h"
|
||||
#include "source/spirv_constant.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
namespace {
|
||||
// Indices of operands in SPIR-V instructions
|
||||
constexpr int kEntryPointFunctionIdInIdx = 1;
|
||||
} // namespace
|
||||
|
||||
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, BasicBlock* new_blk_ptr) {
|
||||
// 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) {
|
||||
auto new_label =
|
||||
MakeUnique<Instruction>(context(), spv::Op::OpLabel, 0, label_id,
|
||||
std::initializer_list<Operand>{});
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*new_label);
|
||||
return new_label;
|
||||
}
|
||||
|
||||
std::unique_ptr<Function> InstrumentPass::StartFunction(
|
||||
uint32_t func_id, const analysis::Type* return_type,
|
||||
const std::vector<const analysis::Type*>& param_types) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Function* func_type = GetFunction(return_type, param_types);
|
||||
|
||||
const std::vector<Operand> operands{
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||
{uint32_t(spv::FunctionControlMask::MaskNone)}},
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_mgr->GetId(func_type)}},
|
||||
};
|
||||
auto func_inst =
|
||||
MakeUnique<Instruction>(context(), spv::Op::OpFunction,
|
||||
type_mgr->GetId(return_type), func_id, operands);
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
|
||||
return MakeUnique<Function>(std::move(func_inst));
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> InstrumentPass::EndFunction() {
|
||||
auto end = MakeUnique<Instruction>(context(), spv::Op::OpFunctionEnd, 0, 0,
|
||||
std::initializer_list<Operand>{});
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(end.get());
|
||||
return end;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> InstrumentPass::AddParameters(
|
||||
Function& func, const std::vector<const analysis::Type*>& param_types) {
|
||||
std::vector<uint32_t> param_ids;
|
||||
param_ids.reserve(param_types.size());
|
||||
for (const analysis::Type* param : param_types) {
|
||||
uint32_t pid = TakeNextId();
|
||||
param_ids.push_back(pid);
|
||||
auto param_inst =
|
||||
MakeUnique<Instruction>(context(), spv::Op::OpFunctionParameter,
|
||||
context()->get_type_mgr()->GetId(param), pid,
|
||||
std::initializer_list<Operand>{});
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(param_inst.get());
|
||||
func.AddParameter(std::move(param_inst));
|
||||
}
|
||||
return param_ids;
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> InstrumentPass::NewName(
|
||||
uint32_t id, const std::string& name_str) {
|
||||
return MakeUnique<Instruction>(
|
||||
context(), spv::Op::OpName, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {id}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}});
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::Gen32BitCvtCode(uint32_t val_id,
|
||||
InstructionBuilder* builder) {
|
||||
// Convert integer value to 32-bit if necessary
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
uint32_t val_ty_id = get_def_use_mgr()->GetDef(val_id)->type_id();
|
||||
analysis::Integer* val_ty = type_mgr->GetType(val_ty_id)->AsInteger();
|
||||
if (val_ty->width() == 32) return val_id;
|
||||
bool is_signed = val_ty->IsSigned();
|
||||
analysis::Integer val_32b_ty(32, is_signed);
|
||||
analysis::Type* val_32b_reg_ty = type_mgr->GetRegisteredType(&val_32b_ty);
|
||||
uint32_t val_32b_reg_ty_id = type_mgr->GetId(val_32b_reg_ty);
|
||||
if (is_signed)
|
||||
return builder->AddUnaryOp(val_32b_reg_ty_id, spv::Op::OpSConvert, val_id)
|
||||
->result_id();
|
||||
else
|
||||
return builder->AddUnaryOp(val_32b_reg_ty_id, spv::Op::OpUConvert, val_id)
|
||||
->result_id();
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GenUintCastCode(uint32_t val_id,
|
||||
InstructionBuilder* builder) {
|
||||
// Convert value to 32-bit if necessary
|
||||
uint32_t val_32b_id = Gen32BitCvtCode(val_id, builder);
|
||||
// Cast value to unsigned if necessary
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
uint32_t val_ty_id = get_def_use_mgr()->GetDef(val_32b_id)->type_id();
|
||||
analysis::Integer* val_ty = type_mgr->GetType(val_ty_id)->AsInteger();
|
||||
if (!val_ty->IsSigned()) return val_32b_id;
|
||||
return builder->AddUnaryOp(GetUintId(), spv::Op::OpBitcast, val_32b_id)
|
||||
->result_id();
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GenVarLoad(uint32_t var_id,
|
||||
InstructionBuilder* builder) {
|
||||
Instruction* var_inst = get_def_use_mgr()->GetDef(var_id);
|
||||
uint32_t type_id = GetPointeeTypeId(var_inst);
|
||||
Instruction* load_inst = builder->AddLoad(type_id, var_id);
|
||||
return load_inst->result_id();
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GenStageInfo(uint32_t stage_idx,
|
||||
InstructionBuilder* builder) {
|
||||
std::vector<uint32_t> ids(4, builder->GetUintConstantId(0));
|
||||
ids[0] = builder->GetUintConstantId(stage_idx);
|
||||
// %289 = OpCompositeConstruct %v4uint %uint_0 %285 %288 %uint_0
|
||||
// TODO(greg-lunarg): Add support for all stages
|
||||
switch (spv::ExecutionModel(stage_idx)) {
|
||||
case spv::ExecutionModel::Vertex: {
|
||||
// Load and store VertexId and InstanceId
|
||||
uint32_t load_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::VertexIndex)),
|
||||
builder);
|
||||
ids[1] = GenUintCastCode(load_id, builder);
|
||||
|
||||
load_id = GenVarLoad(context()->GetBuiltinInputVarId(
|
||||
uint32_t(spv::BuiltIn::InstanceIndex)),
|
||||
builder);
|
||||
ids[2] = GenUintCastCode(load_id, builder);
|
||||
} break;
|
||||
case spv::ExecutionModel::GLCompute:
|
||||
case spv::ExecutionModel::TaskNV:
|
||||
case spv::ExecutionModel::MeshNV:
|
||||
case spv::ExecutionModel::TaskEXT:
|
||||
case spv::ExecutionModel::MeshEXT: {
|
||||
// Load and store GlobalInvocationId.
|
||||
uint32_t load_id = GenVarLoad(context()->GetBuiltinInputVarId(uint32_t(
|
||||
spv::BuiltIn::GlobalInvocationId)),
|
||||
builder);
|
||||
for (uint32_t u = 0; u < 3u; ++u) {
|
||||
ids[u + 1] = builder->AddCompositeExtract(GetUintId(), load_id, {u})
|
||||
->result_id();
|
||||
}
|
||||
} break;
|
||||
case spv::ExecutionModel::Geometry: {
|
||||
// Load and store PrimitiveId and InvocationId.
|
||||
uint32_t load_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)),
|
||||
builder);
|
||||
ids[1] = load_id;
|
||||
load_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::InvocationId)),
|
||||
builder);
|
||||
ids[2] = GenUintCastCode(load_id, builder);
|
||||
} break;
|
||||
case spv::ExecutionModel::TessellationControl: {
|
||||
// Load and store InvocationId and PrimitiveId
|
||||
uint32_t load_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::InvocationId)),
|
||||
builder);
|
||||
ids[1] = GenUintCastCode(load_id, builder);
|
||||
load_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)),
|
||||
builder);
|
||||
ids[2] = load_id;
|
||||
} break;
|
||||
case spv::ExecutionModel::TessellationEvaluation: {
|
||||
// Load and store PrimitiveId and TessCoord.uv
|
||||
uint32_t load_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)),
|
||||
builder);
|
||||
ids[1] = load_id;
|
||||
load_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::TessCoord)),
|
||||
builder);
|
||||
Instruction* uvec3_cast_inst =
|
||||
builder->AddUnaryOp(GetVec3UintId(), spv::Op::OpBitcast, load_id);
|
||||
uint32_t uvec3_cast_id = uvec3_cast_inst->result_id();
|
||||
for (uint32_t u = 0; u < 2u; ++u) {
|
||||
ids[u + 2] =
|
||||
builder->AddCompositeExtract(GetUintId(), uvec3_cast_id, {u})
|
||||
->result_id();
|
||||
}
|
||||
} break;
|
||||
case spv::ExecutionModel::Fragment: {
|
||||
// Load FragCoord and convert to Uint
|
||||
Instruction* frag_coord_inst = builder->AddLoad(
|
||||
GetVec4FloatId(),
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::FragCoord)));
|
||||
Instruction* uint_frag_coord_inst = builder->AddUnaryOp(
|
||||
GetVec4UintId(), spv::Op::OpBitcast, frag_coord_inst->result_id());
|
||||
for (uint32_t u = 0; u < 2u; ++u) {
|
||||
ids[u + 1] =
|
||||
builder
|
||||
->AddCompositeExtract(GetUintId(),
|
||||
uint_frag_coord_inst->result_id(), {u})
|
||||
->result_id();
|
||||
}
|
||||
} break;
|
||||
case spv::ExecutionModel::RayGenerationNV:
|
||||
case spv::ExecutionModel::IntersectionNV:
|
||||
case spv::ExecutionModel::AnyHitNV:
|
||||
case spv::ExecutionModel::ClosestHitNV:
|
||||
case spv::ExecutionModel::MissNV:
|
||||
case spv::ExecutionModel::CallableNV: {
|
||||
// Load and store LaunchIdNV.
|
||||
uint32_t launch_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::LaunchIdNV)),
|
||||
builder);
|
||||
for (uint32_t u = 0; u < 3u; ++u) {
|
||||
ids[u + 1] = builder->AddCompositeExtract(GetUintId(), launch_id, {u})
|
||||
->result_id();
|
||||
}
|
||||
} break;
|
||||
default: { assert(false && "unsupported stage"); } break;
|
||||
}
|
||||
return builder->AddCompositeConstruct(GetVec4UintId(), ids)->result_id();
|
||||
}
|
||||
|
||||
bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) {
|
||||
for (auto& id : ids) {
|
||||
Instruction* id_inst = context()->get_def_use_mgr()->GetDef(id);
|
||||
if (!spvOpcodeIsConstant(id_inst->opcode())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GenReadFunctionCall(
|
||||
uint32_t return_id, uint32_t func_id,
|
||||
const std::vector<uint32_t>& func_call_args,
|
||||
InstructionBuilder* ref_builder) {
|
||||
// If optimizing direct reads and the call has already been generated,
|
||||
// use its result
|
||||
if (opt_direct_reads_) {
|
||||
uint32_t res_id = call2id_[func_call_args];
|
||||
if (res_id != 0) return res_id;
|
||||
}
|
||||
// If the function arguments are all constants, the call can be moved to the
|
||||
// first block of the function where its result can be reused. One example
|
||||
// where this is profitable is for uniform buffer references, of which there
|
||||
// are often many.
|
||||
InstructionBuilder builder(ref_builder->GetContext(),
|
||||
&*ref_builder->GetInsertPoint(),
|
||||
ref_builder->GetPreservedAnalysis());
|
||||
bool insert_in_first_block = opt_direct_reads_ && AllConstant(func_call_args);
|
||||
if (insert_in_first_block) {
|
||||
Instruction* insert_before = &*curr_func_->begin()->tail();
|
||||
builder.SetInsertPoint(insert_before);
|
||||
}
|
||||
uint32_t res_id =
|
||||
builder.AddFunctionCall(return_id, func_id, func_call_args)->result_id();
|
||||
if (insert_in_first_block) call2id_[func_call_args] = res_id;
|
||||
return res_id;
|
||||
}
|
||||
|
||||
bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
|
||||
return inst->opcode() == spv::Op::OpSampledImage ||
|
||||
inst->opcode() == spv::Op::OpImage;
|
||||
}
|
||||
|
||||
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,
|
||||
BasicBlock* block_ptr) {
|
||||
bool changed = false;
|
||||
(*inst)->ForEachInId([&same_blk_post, &same_blk_pre, &block_ptr, &changed,
|
||||
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()));
|
||||
const uint32_t rid = sb_inst->result_id();
|
||||
const uint32_t nid = this->TakeNextId();
|
||||
get_decoration_mgr()->CloneDecorations(rid, nid);
|
||||
sb_inst->SetResultId(nid);
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*sb_inst);
|
||||
(*same_blk_post)[rid] = nid;
|
||||
*iid = nid;
|
||||
changed = true;
|
||||
CloneSameBlockOps(&sb_inst, same_blk_post, same_blk_pre, block_ptr);
|
||||
block_ptr->AddInstruction(std::move(sb_inst));
|
||||
}
|
||||
} else {
|
||||
// Reset same-block op operand if necessary
|
||||
if (*iid != map_itr->second) {
|
||||
*iid = map_itr->second;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (changed) get_def_use_mgr()->AnalyzeInstUse(&**inst);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
analysis::Integer* InstrumentPass::GetInteger(uint32_t width, bool is_signed) {
|
||||
analysis::Integer i(width, is_signed);
|
||||
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&i);
|
||||
assert(type && type->AsInteger());
|
||||
return type->AsInteger();
|
||||
}
|
||||
|
||||
analysis::Struct* InstrumentPass::GetStruct(
|
||||
const std::vector<const analysis::Type*>& fields) {
|
||||
analysis::Struct s(fields);
|
||||
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&s);
|
||||
assert(type && type->AsStruct());
|
||||
return type->AsStruct();
|
||||
}
|
||||
|
||||
analysis::RuntimeArray* InstrumentPass::GetRuntimeArray(
|
||||
const analysis::Type* element) {
|
||||
analysis::RuntimeArray r(element);
|
||||
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&r);
|
||||
assert(type && type->AsRuntimeArray());
|
||||
return type->AsRuntimeArray();
|
||||
}
|
||||
|
||||
analysis::Array* InstrumentPass::GetArray(const analysis::Type* element,
|
||||
uint32_t length) {
|
||||
uint32_t length_id = context()->get_constant_mgr()->GetUIntConstId(length);
|
||||
analysis::Array::LengthInfo length_info{
|
||||
length_id, {analysis::Array::LengthInfo::Case::kConstant, length}};
|
||||
|
||||
analysis::Array r(element, length_info);
|
||||
|
||||
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&r);
|
||||
assert(type && type->AsArray());
|
||||
return type->AsArray();
|
||||
}
|
||||
|
||||
analysis::Function* InstrumentPass::GetFunction(
|
||||
const analysis::Type* return_val,
|
||||
const std::vector<const analysis::Type*>& args) {
|
||||
analysis::Function func(return_val, args);
|
||||
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&func);
|
||||
assert(type && type->AsFunction());
|
||||
return type->AsFunction();
|
||||
}
|
||||
|
||||
analysis::RuntimeArray* InstrumentPass::GetUintXRuntimeArrayType(
|
||||
uint32_t width, analysis::RuntimeArray** rarr_ty) {
|
||||
if (*rarr_ty == nullptr) {
|
||||
*rarr_ty = GetRuntimeArray(GetInteger(width, false));
|
||||
uint32_t uint_arr_ty_id =
|
||||
context()->get_type_mgr()->GetTypeInstruction(*rarr_ty);
|
||||
// By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
|
||||
// a block, and will therefore be decorated with an ArrayStride. Therefore
|
||||
// the undecorated type returned here will not be pre-existing and can
|
||||
// safely be decorated. Since this type is now decorated, it is out of
|
||||
// sync with the TypeManager and therefore the TypeManager must be
|
||||
// invalidated after this pass.
|
||||
assert(get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
|
||||
"used RuntimeArray type returned");
|
||||
get_decoration_mgr()->AddDecorationVal(
|
||||
uint_arr_ty_id, uint32_t(spv::Decoration::ArrayStride), width / 8u);
|
||||
}
|
||||
return *rarr_ty;
|
||||
}
|
||||
|
||||
analysis::RuntimeArray* InstrumentPass::GetUintRuntimeArrayType(
|
||||
uint32_t width) {
|
||||
analysis::RuntimeArray** rarr_ty =
|
||||
(width == 64) ? &uint64_rarr_ty_ : &uint32_rarr_ty_;
|
||||
return GetUintXRuntimeArrayType(width, rarr_ty);
|
||||
}
|
||||
|
||||
void InstrumentPass::AddStorageBufferExt() {
|
||||
if (storage_buffer_ext_defined_) return;
|
||||
if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) {
|
||||
context()->AddExtension("SPV_KHR_storage_buffer_storage_class");
|
||||
}
|
||||
storage_buffer_ext_defined_ = true;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetFloatId() {
|
||||
if (float_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);
|
||||
float_id_ = type_mgr->GetTypeInstruction(reg_float_ty);
|
||||
}
|
||||
return float_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::GetUint64Id() {
|
||||
if (uint64_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Integer uint64_ty(64, false);
|
||||
analysis::Type* reg_uint64_ty = type_mgr->GetRegisteredType(&uint64_ty);
|
||||
uint64_id_ = type_mgr->GetTypeInstruction(reg_uint64_ty);
|
||||
}
|
||||
return uint64_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetUint8Id() {
|
||||
if (uint8_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Integer uint8_ty(8, false);
|
||||
analysis::Type* reg_uint8_ty = type_mgr->GetRegisteredType(&uint8_ty);
|
||||
uint8_id_ = type_mgr->GetTypeInstruction(reg_uint8_ty);
|
||||
}
|
||||
return uint8_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVecUintId(uint32_t len) {
|
||||
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 v_uint_ty(reg_uint_ty, len);
|
||||
analysis::Type* reg_v_uint_ty = type_mgr->GetRegisteredType(&v_uint_ty);
|
||||
uint32_t v_uint_id = type_mgr->GetTypeInstruction(reg_v_uint_ty);
|
||||
return v_uint_id;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVec4UintId() {
|
||||
if (v4uint_id_ == 0) v4uint_id_ = GetVecUintId(4u);
|
||||
return v4uint_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVec3UintId() {
|
||||
if (v3uint_id_ == 0) v3uint_id_ = GetVecUintId(3u);
|
||||
return v3uint_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_;
|
||||
}
|
||||
|
||||
void InstrumentPass::SplitBlock(
|
||||
BasicBlock::iterator inst_itr, UptrVectorIterator<BasicBlock> block_itr,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
// Make sure def/use analysis is done before we start moving instructions
|
||||
// out of function
|
||||
(void)get_def_use_mgr();
|
||||
// Move original block's preceding instructions into first new block
|
||||
std::unique_ptr<BasicBlock> first_blk_ptr;
|
||||
MovePreludeCode(inst_itr, block_itr, &first_blk_ptr);
|
||||
InstructionBuilder builder(
|
||||
context(), &*first_blk_ptr,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
uint32_t split_blk_id = TakeNextId();
|
||||
std::unique_ptr<Instruction> split_label(NewLabel(split_blk_id));
|
||||
(void)builder.AddBranch(split_blk_id);
|
||||
new_blocks->push_back(std::move(first_blk_ptr));
|
||||
// Move remaining instructions into split block and add to new blocks
|
||||
std::unique_ptr<BasicBlock> split_blk_ptr(
|
||||
new BasicBlock(std::move(split_label)));
|
||||
MovePostludeCode(block_itr, &*split_blk_ptr);
|
||||
new_blocks->push_back(std::move(split_blk_ptr));
|
||||
}
|
||||
|
||||
bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
|
||||
InstProcessFunction& pfn) {
|
||||
curr_func_ = func;
|
||||
call2id_.clear();
|
||||
bool first_block_split = false;
|
||||
bool modified = false;
|
||||
// Apply instrumentation function to each instruction.
|
||||
// Using block iterators here because of block erasures and insertions.
|
||||
std::vector<std::unique_ptr<BasicBlock>> new_blks;
|
||||
for (auto bi = func->begin(); bi != func->end(); ++bi) {
|
||||
for (auto ii = bi->begin(); ii != bi->end();) {
|
||||
// Split all executable instructions out of first block into a following
|
||||
// block. This will allow function calls to be inserted into the first
|
||||
// block without interfering with the instrumentation algorithm.
|
||||
if (opt_direct_reads_ && !first_block_split) {
|
||||
if (ii->opcode() != spv::Op::OpVariable) {
|
||||
SplitBlock(ii, bi, &new_blks);
|
||||
first_block_split = true;
|
||||
}
|
||||
} else {
|
||||
pfn(ii, bi, stage_idx, &new_blks);
|
||||
}
|
||||
// If no new code, continue
|
||||
if (new_blks.size() == 0) {
|
||||
++ii;
|
||||
continue;
|
||||
}
|
||||
// Add new blocks to label id map
|
||||
for (auto& blk : new_blks) id2block_[blk->id()] = &*blk;
|
||||
// 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() == spv::Op::OpPhi ||
|
||||
ii->opcode() == spv::Op::OpCopyObject)
|
||||
++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;
|
||||
// Don't process input and output functions
|
||||
for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
|
||||
for (auto& ofn : param2output_func_id_) done.insert(ofn.second);
|
||||
// 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
|
||||
context()->AddCalls(fn, roots);
|
||||
modified = InstrumentFunction(fn, stage_idx, pfn) || modified;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
|
||||
uint32_t stage_id;
|
||||
if (use_stage_info_) {
|
||||
// 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.
|
||||
spv::ExecutionModel stage = context()->GetStage();
|
||||
// Check for supported stages
|
||||
if (stage != spv::ExecutionModel::Vertex &&
|
||||
stage != spv::ExecutionModel::Fragment &&
|
||||
stage != spv::ExecutionModel::Geometry &&
|
||||
stage != spv::ExecutionModel::GLCompute &&
|
||||
stage != spv::ExecutionModel::TessellationControl &&
|
||||
stage != spv::ExecutionModel::TessellationEvaluation &&
|
||||
stage != spv::ExecutionModel::TaskNV &&
|
||||
stage != spv::ExecutionModel::MeshNV &&
|
||||
stage != spv::ExecutionModel::RayGenerationNV &&
|
||||
stage != spv::ExecutionModel::IntersectionNV &&
|
||||
stage != spv::ExecutionModel::AnyHitNV &&
|
||||
stage != spv::ExecutionModel::ClosestHitNV &&
|
||||
stage != spv::ExecutionModel::MissNV &&
|
||||
stage != spv::ExecutionModel::CallableNV &&
|
||||
stage != spv::ExecutionModel::TaskEXT &&
|
||||
stage != spv::ExecutionModel::MeshEXT) {
|
||||
if (consumer()) {
|
||||
std::string message = "Stage not supported by instrumentation";
|
||||
consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
stage_id = static_cast<uint32_t>(stage);
|
||||
} else {
|
||||
stage_id = 0;
|
||||
}
|
||||
// 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_id);
|
||||
return modified;
|
||||
}
|
||||
|
||||
void InstrumentPass::InitializeInstrument() {
|
||||
float_id_ = 0;
|
||||
v4float_id_ = 0;
|
||||
uint_id_ = 0;
|
||||
uint64_id_ = 0;
|
||||
uint8_id_ = 0;
|
||||
v4uint_id_ = 0;
|
||||
v3uint_id_ = 0;
|
||||
bool_id_ = 0;
|
||||
void_id_ = 0;
|
||||
storage_buffer_ext_defined_ = false;
|
||||
uint32_rarr_ty_ = nullptr;
|
||||
uint64_rarr_ty_ = nullptr;
|
||||
|
||||
// clear collections
|
||||
id2function_.clear();
|
||||
id2block_.clear();
|
||||
|
||||
// clear maps
|
||||
param2input_func_id_.clear();
|
||||
param2output_func_id_.clear();
|
||||
|
||||
// Initialize function and block maps.
|
||||
for (auto& fn : *get_module()) {
|
||||
id2function_[fn.result_id()] = &fn;
|
||||
for (auto& blk : fn) {
|
||||
id2block_[blk.id()] = &blk;
|
||||
}
|
||||
}
|
||||
|
||||
// Remember original instruction offsets
|
||||
uint32_t module_offset = 0;
|
||||
Module* module = get_module();
|
||||
for (auto& i : context()->capabilities()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->extensions()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->ext_inst_imports()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
++module_offset; // memory_model
|
||||
for (auto& i : module->entry_points()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->execution_modes()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->debugs1()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->debugs2()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->debugs3()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->ext_inst_debuginfo()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->annotations()) {
|
||||
(void)i;
|
||||
++module_offset;
|
||||
}
|
||||
for (auto& i : module->types_values()) {
|
||||
module_offset += 1;
|
||||
module_offset += static_cast<uint32_t>(i.dbg_line_insts().size());
|
||||
}
|
||||
|
||||
auto curr_fn = get_module()->begin();
|
||||
for (; curr_fn != get_module()->end(); ++curr_fn) {
|
||||
// Count function instruction
|
||||
module_offset += 1;
|
||||
curr_fn->ForEachParam(
|
||||
[&module_offset](const Instruction*) { module_offset += 1; }, true);
|
||||
for (auto& blk : *curr_fn) {
|
||||
// Count label
|
||||
module_offset += 1;
|
||||
for (auto& inst : blk) {
|
||||
module_offset += static_cast<uint32_t>(inst.dbg_line_insts().size());
|
||||
uid2offset_[inst.unique_id()] = module_offset;
|
||||
module_offset += 1;
|
||||
}
|
||||
}
|
||||
// Count function end instruction
|
||||
module_offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
@ -1,326 +0,0 @@
|
||||
// Copyright (c) 2018 The Khronos Group Inc.
|
||||
// Copyright (c) 2018 Valve Corporation
|
||||
// Copyright (c) 2018 LunarG Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef LIBSPIRV_OPT_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 permanently assigned and fixed and documented by
|
||||
// the kDebugOutput* static consts.
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
class InstrumentPass : public Pass {
|
||||
using cbb_ptr = const BasicBlock*;
|
||||
|
||||
public:
|
||||
using InstProcessFunction =
|
||||
std::function<void(BasicBlock::iterator, UptrVectorIterator<BasicBlock>,
|
||||
uint32_t, std::vector<std::unique_ptr<BasicBlock>>*)>;
|
||||
|
||||
~InstrumentPass() override = default;
|
||||
|
||||
IRContext::Analysis GetPreservedAnalyses() override {
|
||||
return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
|
||||
IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap |
|
||||
IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Create instrumentation pass for |validation_id| which utilizes descriptor
|
||||
// set |desc_set| for debug input and output buffers and writes |shader_id|
|
||||
// into debug output records. |opt_direct_reads| indicates that the pass
|
||||
// will see direct input buffer reads and should prepare to optimize them.
|
||||
InstrumentPass(uint32_t desc_set, uint32_t shader_id, bool opt_direct_reads,
|
||||
bool use_stage_info)
|
||||
: Pass(),
|
||||
desc_set_(desc_set),
|
||||
shader_id_(shader_id),
|
||||
opt_direct_reads_(opt_direct_reads),
|
||||
use_stage_info_(use_stage_info) {}
|
||||
|
||||
// Initialize state for instrumentation of module.
|
||||
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,
|
||||
BasicBlock* new_blk_ptr);
|
||||
|
||||
// Return true if all instructions in |ids| are constants or spec constants.
|
||||
bool AllConstant(const std::vector<uint32_t>& ids);
|
||||
|
||||
uint32_t GenReadFunctionCall(uint32_t return_id, uint32_t func_id,
|
||||
const std::vector<uint32_t>& args,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Generate code to convert integer |value_id| to 32bit, if needed. Return
|
||||
// an id to the 32bit equivalent.
|
||||
uint32_t Gen32BitCvtCode(uint32_t value_id, InstructionBuilder* builder);
|
||||
|
||||
// Generate code to cast integer |value_id| to 32bit unsigned, if needed.
|
||||
// Return an id to the Uint equivalent.
|
||||
uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
|
||||
|
||||
std::unique_ptr<Function> StartFunction(
|
||||
uint32_t func_id, const analysis::Type* return_type,
|
||||
const std::vector<const analysis::Type*>& param_types);
|
||||
|
||||
std::vector<uint32_t> AddParameters(
|
||||
Function& func, const std::vector<const analysis::Type*>& param_types);
|
||||
|
||||
std::unique_ptr<Instruction> EndFunction();
|
||||
|
||||
// Return new label.
|
||||
std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
|
||||
|
||||
// Set the name function parameter or local variable
|
||||
std::unique_ptr<Instruction> NewName(uint32_t id,
|
||||
const std::string& name_str);
|
||||
|
||||
// Return id for 32-bit unsigned type
|
||||
uint32_t GetUintId();
|
||||
|
||||
// Return id for 64-bit unsigned type
|
||||
uint32_t GetUint64Id();
|
||||
|
||||
// Return id for 8-bit unsigned type
|
||||
uint32_t GetUint8Id();
|
||||
|
||||
// Return id for 32-bit unsigned type
|
||||
uint32_t GetBoolId();
|
||||
|
||||
// Return id for void type
|
||||
uint32_t GetVoidId();
|
||||
|
||||
// Get registered type structures
|
||||
analysis::Integer* GetInteger(uint32_t width, bool is_signed);
|
||||
analysis::Struct* GetStruct(const std::vector<const analysis::Type*>& fields);
|
||||
analysis::RuntimeArray* GetRuntimeArray(const analysis::Type* element);
|
||||
analysis::Array* GetArray(const analysis::Type* element, uint32_t size);
|
||||
analysis::Function* GetFunction(
|
||||
const analysis::Type* return_val,
|
||||
const std::vector<const analysis::Type*>& args);
|
||||
|
||||
// Return pointer to type for runtime array of uint
|
||||
analysis::RuntimeArray* GetUintXRuntimeArrayType(
|
||||
uint32_t width, analysis::RuntimeArray** rarr_ty);
|
||||
|
||||
// Return pointer to type for runtime array of uint
|
||||
analysis::RuntimeArray* GetUintRuntimeArrayType(uint32_t width);
|
||||
|
||||
// Add storage buffer extension if needed
|
||||
void AddStorageBufferExt();
|
||||
|
||||
// Return id for 32-bit float type
|
||||
uint32_t GetFloatId();
|
||||
|
||||
// Return id for v4float type
|
||||
uint32_t GetVec4FloatId();
|
||||
|
||||
// Return id for uint vector type of |length|
|
||||
uint32_t GetVecUintId(uint32_t length);
|
||||
|
||||
// Return id for v4uint type
|
||||
uint32_t GetVec4UintId();
|
||||
|
||||
// Return id for v3uint type
|
||||
uint32_t GetVec3UintId();
|
||||
|
||||
// Split block |block_itr| into two new blocks where the second block
|
||||
// contains |inst_itr| and place in |new_blocks|.
|
||||
void SplitBlock(BasicBlock::iterator inst_itr,
|
||||
UptrVectorIterator<BasicBlock> block_itr,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
// 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.
|
||||
virtual 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);
|
||||
|
||||
// Generate instructions into |builder| which will load |var_id| and return
|
||||
// its result id.
|
||||
uint32_t GenVarLoad(uint32_t var_id, InstructionBuilder* builder);
|
||||
|
||||
uint32_t GenStageInfo(uint32_t stage_idx, 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,
|
||||
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 instruction's unique id to offset in original file.
|
||||
std::unordered_map<uint32_t, uint32_t> uid2offset_;
|
||||
|
||||
// id for debug output function
|
||||
std::unordered_map<uint32_t, uint32_t> param2output_func_id_;
|
||||
|
||||
// ids for debug input functions
|
||||
std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
|
||||
|
||||
// id for 32-bit float type
|
||||
uint32_t float_id_{0};
|
||||
|
||||
// id for v4float type
|
||||
uint32_t v4float_id_{0};
|
||||
|
||||
// id for v4uint type
|
||||
uint32_t v4uint_id_{0};
|
||||
|
||||
// id for v3uint type
|
||||
uint32_t v3uint_id_{0};
|
||||
|
||||
// id for 32-bit unsigned type
|
||||
uint32_t uint_id_{0};
|
||||
|
||||
// id for 64-bit unsigned type
|
||||
uint32_t uint64_id_{0};
|
||||
|
||||
// id for 8-bit unsigned type
|
||||
uint32_t uint8_id_{0};
|
||||
|
||||
// id for bool type
|
||||
uint32_t bool_id_{0};
|
||||
|
||||
// id for void type
|
||||
uint32_t void_id_{0};
|
||||
|
||||
// boolean to remember storage buffer extension
|
||||
bool storage_buffer_ext_defined_{false};
|
||||
|
||||
// runtime array of uint type
|
||||
analysis::RuntimeArray* uint64_rarr_ty_{nullptr};
|
||||
|
||||
// runtime array of uint type
|
||||
analysis::RuntimeArray* uint32_rarr_ty_{nullptr};
|
||||
|
||||
// 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_;
|
||||
|
||||
// Map function calls to result id. Clear for every function.
|
||||
// This is for debug input reads with constant arguments that
|
||||
// have been generated into the first block of the function.
|
||||
// This mechanism is used to avoid multiple identical debug
|
||||
// input buffer reads.
|
||||
struct vector_hash_ {
|
||||
std::size_t operator()(const std::vector<uint32_t>& v) const {
|
||||
std::size_t hash = v.size();
|
||||
for (auto& u : v) {
|
||||
hash ^= u + 0x9e3779b9 + (hash << 11) + (hash >> 21);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
std::unordered_map<std::vector<uint32_t>, uint32_t, vector_hash_> call2id_;
|
||||
|
||||
// Function currently being instrumented
|
||||
Function* curr_func_{nullptr};
|
||||
|
||||
// Optimize direct debug input buffer reads. Specifically, move all such
|
||||
// reads with constant args to first block and reuse them.
|
||||
const bool opt_direct_reads_;
|
||||
|
||||
// Set true if the instrumentation needs to know the current stage.
|
||||
// Note that this does not work with multi-stage modules.
|
||||
const bool use_stage_info_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // LIBSPIRV_OPT_INSTRUMENT_PASS_H_
|
@ -462,13 +462,6 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
|
||||
RegisterPass(CreateConvertRelaxedToHalfPass());
|
||||
} else if (pass_name == "relax-float-ops") {
|
||||
RegisterPass(CreateRelaxFloatOpsPass());
|
||||
} else if (pass_name == "inst-debug-printf") {
|
||||
// This private option is not for user consumption.
|
||||
// It is here to assist in debugging and fixing the debug printf
|
||||
// instrumentation pass.
|
||||
// For users who wish to utilize debug printf, see the white paper at
|
||||
// https://www.lunarg.com/wp-content/uploads/2021/08/Using-Debug-Printf-02August2021.pdf
|
||||
RegisterPass(CreateInstDebugPrintfPass(7, 23));
|
||||
} else if (pass_name == "simplify-instructions") {
|
||||
RegisterPass(CreateSimplificationPass());
|
||||
} else if (pass_name == "ssa-rewrite") {
|
||||
@ -1040,12 +1033,6 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
|
||||
MakeUnique<opt::UpgradeMemoryModel>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
|
||||
uint32_t shader_id) {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::InstDebugPrintfPass>(desc_set, shader_id));
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateConvertRelaxedToHalfPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::ConvertToHalfPass>());
|
||||
|
@ -48,7 +48,6 @@
|
||||
#include "source/opt/if_conversion.h"
|
||||
#include "source/opt/inline_exhaustive_pass.h"
|
||||
#include "source/opt/inline_opaque_pass.h"
|
||||
#include "source/opt/inst_debug_printf_pass.h"
|
||||
#include "source/opt/interface_var_sroa.h"
|
||||
#include "source/opt/interp_fixup_pass.h"
|
||||
#include "source/opt/invocation_interlock_placement_pass.h"
|
||||
|
@ -60,7 +60,6 @@ add_spvtools_unittest(TARGET opt
|
||||
inline_opaque_test.cpp
|
||||
inline_test.cpp
|
||||
insert_extract_elim_test.cpp
|
||||
inst_debug_printf_test.cpp
|
||||
instruction_list_test.cpp
|
||||
instruction_test.cpp
|
||||
interface_var_sroa_test.cpp
|
||||
|
@ -1,207 +0,0 @@
|
||||
// Copyright (c) 2020-2022 Valve Corporation
|
||||
// Copyright (c) 2020-2022 LunarG Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Debug Printf Instrumentation Tests.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "test/opt/pass_fixture.h"
|
||||
#include "test/opt/pass_utils.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
namespace {
|
||||
|
||||
static const std::string kOutputDecorations = R"(
|
||||
; CHECK: OpDecorate [[output_buffer_type:%inst_printf_OutputBuffer]] Block
|
||||
; CHECK: OpMemberDecorate [[output_buffer_type]] 0 Offset 0
|
||||
; CHECK: OpMemberDecorate [[output_buffer_type]] 1 Offset 4
|
||||
; CHECK: OpMemberDecorate [[output_buffer_type]] 2 Offset 8
|
||||
; CHECK: OpDecorate [[output_buffer_var:%\w+]] DescriptorSet 7
|
||||
; CHECK: OpDecorate [[output_buffer_var]] Binding 3
|
||||
)";
|
||||
|
||||
static const std::string kOutputGlobals = R"(
|
||||
; CHECK: [[output_buffer_type]] = OpTypeStruct %uint %uint %_runtimearr_uint
|
||||
; CHECK: [[output_ptr_type:%\w+]] = OpTypePointer StorageBuffer [[output_buffer_type]]
|
||||
; CHECK: [[output_buffer_var]] = OpVariable [[output_ptr_type]] StorageBuffer
|
||||
)";
|
||||
|
||||
using InstDebugPrintfTest = PassTest<::testing::Test>;
|
||||
|
||||
TEST_F(InstDebugPrintfTest, V4Float32) {
|
||||
// SamplerState g_sDefault;
|
||||
// Texture2D g_tColor;
|
||||
//
|
||||
// struct PS_INPUT
|
||||
// {
|
||||
// float2 vBaseTexCoord : TEXCOORD0;
|
||||
// };
|
||||
//
|
||||
// struct PS_OUTPUT
|
||||
// {
|
||||
// float4 vDiffuse : SV_Target0;
|
||||
// };
|
||||
//
|
||||
// PS_OUTPUT MainPs(PS_INPUT i)
|
||||
// {
|
||||
// PS_OUTPUT o;
|
||||
//
|
||||
// o.vDiffuse.rgba = g_tColor.Sample(g_sDefault, (i.vBaseTexCoord.xy).xy);
|
||||
// debugPrintfEXT("diffuse: %v4f", o.vDiffuse.rgba);
|
||||
// return o;
|
||||
// }
|
||||
|
||||
const std::string defs =
|
||||
R"(OpCapability Shader
|
||||
OpExtension "SPV_KHR_non_semantic_info"
|
||||
%1 = OpExtInstImport "NonSemantic.DebugPrintf"
|
||||
; CHECK-NOT: OpExtension "SPV_KHR_non_semantic_info"
|
||||
; CHECK-NOT: %1 = OpExtInstImport "NonSemantic.DebugPrintf"
|
||||
; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "MainPs" %3 %4
|
||||
; CHECK: OpEntryPoint Fragment %2 "MainPs" %3 %4
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
%5 = OpString "Color is %vn"
|
||||
)";
|
||||
|
||||
// clang-format off
|
||||
const std::string decorates =
|
||||
R"(OpDecorate %6 DescriptorSet 0
|
||||
OpDecorate %6 Binding 1
|
||||
OpDecorate %7 DescriptorSet 0
|
||||
OpDecorate %7 Binding 0
|
||||
OpDecorate %3 Location 0
|
||||
OpDecorate %4 Location 0
|
||||
)" + kOutputDecorations;
|
||||
|
||||
const std::string globals =
|
||||
R"(%void = OpTypeVoid
|
||||
%9 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v2float = OpTypeVector %float 2
|
||||
%v4float = OpTypeVector %float 4
|
||||
%13 = OpTypeImage %float 2D 0 0 0 1 Unknown
|
||||
%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
|
||||
%6 = OpVariable %_ptr_UniformConstant_13 UniformConstant
|
||||
%15 = OpTypeSampler
|
||||
%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
|
||||
%7 = OpVariable %_ptr_UniformConstant_15 UniformConstant
|
||||
%17 = OpTypeSampledImage %13
|
||||
%_ptr_Input_v2float = OpTypePointer Input %v2float
|
||||
%3 = OpVariable %_ptr_Input_v2float Input
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%4 = OpVariable %_ptr_Output_v4float Output
|
||||
; CHECK: %uint = OpTypeInt 32 0
|
||||
; CHECK: [[func_type:%\w+]] = OpTypeFunction %void %uint %uint %uint %uint %uint %uint %uint
|
||||
; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
|
||||
)" + kOutputGlobals + R"(
|
||||
; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
|
||||
; CHECK: %bool = OpTypeBool
|
||||
)";
|
||||
// clang-format on
|
||||
|
||||
const std::string main =
|
||||
R"(%2 = OpFunction %void None %9
|
||||
%20 = OpLabel
|
||||
%21 = OpLoad %v2float %3
|
||||
%22 = OpLoad %13 %6
|
||||
%23 = OpLoad %15 %7
|
||||
%24 = OpSampledImage %17 %22 %23
|
||||
%25 = OpImageSampleImplicitLod %v4float %24 %21
|
||||
%26 = OpExtInst %void %1 1 %5 %25
|
||||
; CHECK-NOT: %26 = OpExtInst %void %1 1 %5 %25
|
||||
; CHECK: {{%\w+}} = OpCompositeExtract %float %25 0
|
||||
; CHECK: {{%\w+}} = OpBitcast %uint {{%\w+}}
|
||||
; CHECK: {{%\w+}} = OpCompositeExtract %float %25 1
|
||||
; CHECK: {{%\w+}} = OpBitcast %uint {{%\w+}}
|
||||
; CHECK: {{%\w+}} = OpCompositeExtract %float %25 2
|
||||
; CHECK: {{%\w+}} = OpBitcast %uint {{%\w+}}
|
||||
; CHECK: {{%\w+}} = OpCompositeExtract %float %25 3
|
||||
; CHECK: {{%\w+}} = OpBitcast %uint {{%\w+}}
|
||||
; CHECK: {{%\w+}} = OpFunctionCall %void %inst_printf_stream_write_5 %uint_23 %uint_36 %uint_5 {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}}
|
||||
; CHECK: OpBranch {{%\w+}}
|
||||
; CHECK: {{%\w+}} = OpLabel
|
||||
OpStore %4 %25
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string output_func = R"(
|
||||
; CHECK: %inst_printf_stream_write_5 = OpFunction %void None {{%\w+}}
|
||||
; CHECK: [[sw_shader_id:%\w+]] = OpFunctionParameter %uint
|
||||
; CHECK: [[sw_inst_idx:%\w+]] = OpFunctionParameter %uint
|
||||
; CHECK: [[sw_param_1:%\w+]] = OpFunctionParameter %uint
|
||||
; CHECK: [[sw_param_2:%\w+]] = OpFunctionParameter %uint
|
||||
; CHECK: [[sw_param_3:%\w+]] = OpFunctionParameter %uint
|
||||
; CHECK: [[sw_param_4:%\w+]] = OpFunctionParameter %uint
|
||||
; CHECK: [[sw_param_5:%\w+]] = OpFunctionParameter %uint
|
||||
; CHECK: {{%\w+}} = OpLabel
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1
|
||||
; CHECK: {{%\w+}} = OpAtomicIAdd %uint {{%\w+}} %uint_4 %uint_0 %uint_8
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_8
|
||||
; CHECK: {{%\w+}} = OpArrayLength %uint [[output_buffer_var]] 2
|
||||
; CHECK: {{%\w+}} = OpULessThanEqual %bool {{%\w+}} {{%\w+}}
|
||||
; CHECK: OpSelectionMerge {{%\w+}} None
|
||||
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
|
||||
; CHECK: {{%\w+}} = OpLabel
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_0
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
|
||||
; CHECK: OpStore {{%\w+}} %uint_8
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_1
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
|
||||
; CHECK: OpStore {{%\w+}} [[sw_shader_id]]
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_2
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
|
||||
; CHECK: OpStore {{%\w+}} [[sw_inst_idx]]
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
|
||||
; CHECK: OpStore {{%\w+}} [[sw_param_1]]
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
|
||||
; CHECK: OpStore {{%\w+}} [[sw_param_2]]
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
|
||||
; CHECK: OpStore {{%\w+}} [[sw_param_3]]
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_6
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
|
||||
; CHECK: OpStore {{%\w+}} [[sw_param_4]]
|
||||
; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_7
|
||||
; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_2 {{%\w+}}
|
||||
; CHECK: OpStore {{%\w+}} [[sw_param_5]]
|
||||
; CHECK: OpBranch {{%\w+}}
|
||||
; CHECK: {{%\w+}} = OpLabel
|
||||
; CHECK: OpReturn
|
||||
; CHECK: OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SinglePassRunAndMatch<InstDebugPrintfPass>(
|
||||
defs + decorates + globals + main + output_func, true);
|
||||
}
|
||||
|
||||
// TODO(greg-lunarg): Add tests to verify handling of these cases:
|
||||
//
|
||||
// Compute shader
|
||||
// Geometry shader
|
||||
// Tessellation control shader
|
||||
// Tessellation eval shader
|
||||
// Vertex shader
|
||||
|
||||
} // namespace
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
Loading…
Reference in New Issue
Block a user