#include "inst_debug_printf_pass.h"
#include "source/spirv_constant.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: {
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: {
Instruction* f32_inst = builder->AddUnaryOp(
GetFloatId(), spv::Op::OpFConvert, val_inst->result_id());
GenOutputValues(f32_inst, val_ids, builder);
return;
}
case 64: {
Instruction* ui64_inst = builder->AddUnaryOp(
GetUint64Id(), spv::Op::OpBitcast, val_inst->result_id());
GenOutputValues(ui64_inst, val_ids, builder);
return;
}
case 32: {
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()) {
ui64_inst = builder->AddUnaryOp(GetUint64Id(), spv::Op::OpBitcast,
val_inst->result_id());
}
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()) {
ui8_inst = builder->AddUnaryOp(GetUint8Id(), spv::Op::OpBitcast,
val_inst->result_id());
}
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()) {
ui32_inst = builder->AddUnaryOp(GetUintId(), spv::Op::OpBitcast,
val_inst->result_id());
}
val_ids->push_back(ui32_inst->result_id());
return;
}
default:
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);
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) {
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) {
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;
(void)get_def_use_mgr();
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));
GenOutputCode(printf_inst, new_blocks);
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);
new_blk_ptr.reset(new BasicBlock(std::move(rem_label)));
builder.SetInsertPoint(&*new_blk_ptr);
MovePostludeCode(ref_block_itr, &*new_blk_ptr);
new_blocks->push_back(std::move(new_blk_ptr));
}
uint32_t InstDebugPrintfPass::GetOutputBufferId() {
if (output_buffer_id_ == 0) {
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);
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)) {
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) {
uint32_t val_id = GenUintCastCode(field_value_id, builder);
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,
};
if (param2output_func_id_[param_cnt] == 0) {
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);
auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
InstructionBuilder builder(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
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)});
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();
Instruction* obuf_new_sz_inst =
builder.AddIAdd(GetUintId(), obuf_curr_sz_id,
builder.GetUintConstantId(obuf_record_sz));
Instruction* obuf_bnd_inst =
builder.AddIdLiteralOp(GetUintId(), spv::Op::OpArrayLength,
GetOutputBufferId(), kDebugOutputDataOffset);
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));
output_func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(write_label));
builder.SetInsertPoint(&*new_blk_ptr);
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutSize,
builder.GetUintConstantId(obuf_record_sz),
&builder);
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutShaderId,
param_ids[kShaderId], &builder);
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutInstructionIdx,
param_ids[kInstructionIndex], &builder);
for (uint32_t i = 0; i < param_cnt; ++i) {
GenDebugOutputFieldCode(obuf_curr_sz_id, first_param_offset + i,
param_ids[kFirstParam + i], &builder);
}
(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);
(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 += std::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) {
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() {
InitializeInstrument();
output_buffer_id_ = 0;
output_buffer_ptr_id_ = 0;
}
Pass::Status InstDebugPrintfPass::ProcessImpl() {
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);
Instruction* ext_inst_import_inst =
get_def_use_mgr()->GetDef(ext_inst_printf_id_);
context()->KillInst(ext_inst_import_inst);
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();
}
}
}