SPIRV-Tools/source/opt/debug_info_manager.cpp
Steven Perron d85446fd7e
[OPT] Fix generating debugLocalVariable from debugGlobalVariable (#5803)
The code converting the global to local was generating an extra operand
that was incorrect. Fixed the code, and added a unit test.

Fixes #5776
2024-09-16 10:11:22 -04:00

946 lines
37 KiB
C++

// Copyright (c) 2020-2022 Google LLC
// Copyright (c) 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.
#include "source/opt/debug_info_manager.h"
#include <cassert>
#include "source/opt/ir_context.h"
// Constants for OpenCL.DebugInfo.100 & NonSemantic.Shader.DebugInfo.100
// extension instructions.
namespace spvtools {
namespace opt {
namespace analysis {
namespace {
constexpr uint32_t kOpLineOperandLineIndex = 1;
constexpr uint32_t kLineOperandIndexDebugFunction = 7;
constexpr uint32_t kLineOperandIndexDebugLexicalBlock = 5;
constexpr uint32_t kLineOperandIndexDebugLine = 5;
constexpr uint32_t kDebugFunctionOperandFunctionIndex = 13;
constexpr uint32_t kDebugFunctionDefinitionOperandDebugFunctionIndex = 4;
constexpr uint32_t kDebugFunctionDefinitionOperandOpFunctionIndex = 5;
constexpr uint32_t kDebugFunctionOperandParentIndex = 9;
constexpr uint32_t kDebugTypeCompositeOperandParentIndex = 9;
constexpr uint32_t kDebugLexicalBlockOperandParentIndex = 7;
constexpr uint32_t kDebugInlinedAtOperandInlinedIndex = 6;
constexpr uint32_t kDebugExpressOperandOperationIndex = 4;
constexpr uint32_t kDebugDeclareOperandLocalVariableIndex = 4;
constexpr uint32_t kDebugDeclareOperandVariableIndex = 5;
constexpr uint32_t kDebugValueOperandExpressionIndex = 6;
constexpr uint32_t kDebugOperationOperandOperationIndex = 4;
constexpr uint32_t kOpVariableOperandStorageClassIndex = 2;
constexpr uint32_t kDebugLocalVariableOperandParentIndex = 9;
constexpr uint32_t kExtInstInstructionInIdx = 1;
constexpr uint32_t kDebugGlobalVariableOperandFlagsIndex = 12;
constexpr uint32_t kDebugLocalVariableOperandFlagsIndex = 10;
void SetInlinedOperand(Instruction* dbg_inlined_at, uint32_t inlined_operand) {
assert(dbg_inlined_at);
assert(dbg_inlined_at->GetCommonDebugOpcode() ==
CommonDebugInfoDebugInlinedAt);
if (dbg_inlined_at->NumOperands() <= kDebugInlinedAtOperandInlinedIndex) {
dbg_inlined_at->AddOperand(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inlined_operand}});
} else {
dbg_inlined_at->SetOperand(kDebugInlinedAtOperandInlinedIndex,
{inlined_operand});
}
}
uint32_t GetInlinedOperand(Instruction* dbg_inlined_at) {
assert(dbg_inlined_at);
assert(dbg_inlined_at->GetCommonDebugOpcode() ==
CommonDebugInfoDebugInlinedAt);
if (dbg_inlined_at->NumOperands() <= kDebugInlinedAtOperandInlinedIndex)
return kNoInlinedAt;
return dbg_inlined_at->GetSingleWordOperand(
kDebugInlinedAtOperandInlinedIndex);
}
bool IsEmptyDebugExpression(Instruction* instr) {
return (instr->GetCommonDebugOpcode() == CommonDebugInfoDebugExpression) &&
instr->NumOperands() == kDebugExpressOperandOperationIndex;
}
} // namespace
DebugInfoManager::DebugInfoManager(IRContext* c) : context_(c) {
AnalyzeDebugInsts(*c->module());
}
uint32_t DebugInfoManager::GetDbgSetImportId() {
uint32_t setId =
context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo();
if (setId == 0) {
setId =
context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo();
}
return setId;
}
Instruction* DebugInfoManager::GetDbgInst(uint32_t id) {
auto dbg_inst_it = id_to_dbg_inst_.find(id);
return dbg_inst_it == id_to_dbg_inst_.end() ? nullptr : dbg_inst_it->second;
}
void DebugInfoManager::RegisterDbgInst(Instruction* inst) {
assert(inst->NumInOperands() != 0 &&
(GetDbgSetImportId() == inst->GetInOperand(0).words[0]) &&
"Given instruction is not a debug instruction");
id_to_dbg_inst_[inst->result_id()] = inst;
}
void DebugInfoManager::RegisterDbgFunction(Instruction* inst) {
if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) {
auto fn_id = inst->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex);
// Do not register function that has been optimized away.
auto fn_inst = GetDbgInst(fn_id);
if (fn_inst != nullptr) {
assert(GetDbgInst(fn_id)->GetOpenCL100DebugOpcode() ==
OpenCLDebugInfo100DebugInfoNone);
return;
}
assert(
fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() &&
"Register DebugFunction for a function that already has DebugFunction");
fn_id_to_dbg_fn_[fn_id] = inst;
} else if (inst->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
auto fn_id = inst->GetSingleWordOperand(
kDebugFunctionDefinitionOperandOpFunctionIndex);
auto fn_inst = GetDbgInst(inst->GetSingleWordOperand(
kDebugFunctionDefinitionOperandDebugFunctionIndex));
assert(fn_inst && fn_inst->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugFunction);
assert(fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() &&
"Register DebugFunctionDefinition for a function that already has "
"DebugFunctionDefinition");
fn_id_to_dbg_fn_[fn_id] = fn_inst;
} else {
assert(false && "inst is not a DebugFunction");
}
}
void DebugInfoManager::RegisterDbgDeclare(uint32_t var_id,
Instruction* dbg_declare) {
assert(dbg_declare->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare ||
dbg_declare->GetCommonDebugOpcode() == CommonDebugInfoDebugValue);
auto dbg_decl_itr = var_id_to_dbg_decl_.find(var_id);
if (dbg_decl_itr == var_id_to_dbg_decl_.end()) {
var_id_to_dbg_decl_[var_id] = {dbg_declare};
} else {
dbg_decl_itr->second.insert(dbg_declare);
}
}
// Create new constant directly into global value area, bypassing the
// Constant manager. This is used when the DefUse or Constant managers
// are invalid and cannot be regenerated due to the module being in an
// inconsistent state e.g. in the middle of significant modification
// such as inlining. Invalidate Constant and DefUse managers if used.
uint32_t AddNewConstInGlobals(IRContext* context, uint32_t const_value) {
uint32_t id = context->TakeNextId();
std::unique_ptr<Instruction> new_const(new Instruction(
context, spv::Op::OpConstant, context->get_type_mgr()->GetUIntTypeId(),
id,
{
{spv_operand_type_t::SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER,
{const_value}},
}));
context->module()->AddGlobalValue(std::move(new_const));
context->InvalidateAnalyses(IRContext::kAnalysisConstants);
context->InvalidateAnalyses(IRContext::kAnalysisDefUse);
return id;
}
uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line,
const DebugScope& scope) {
uint32_t setId = GetDbgSetImportId();
if (setId == 0) return kNoInlinedAt;
spv_operand_type_t line_number_type =
spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER;
// In NonSemantic.Shader.DebugInfo.100, all constants are IDs of OpConstant,
// not literals.
if (setId ==
context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo())
line_number_type = spv_operand_type_t::SPV_OPERAND_TYPE_ID;
uint32_t line_number = 0;
if (line == nullptr) {
auto* lexical_scope_inst = GetDbgInst(scope.GetLexicalScope());
if (lexical_scope_inst == nullptr) return kNoInlinedAt;
CommonDebugInfoInstructions debug_opcode =
lexical_scope_inst->GetCommonDebugOpcode();
switch (debug_opcode) {
case CommonDebugInfoDebugFunction:
line_number = lexical_scope_inst->GetSingleWordOperand(
kLineOperandIndexDebugFunction);
break;
case CommonDebugInfoDebugLexicalBlock:
line_number = lexical_scope_inst->GetSingleWordOperand(
kLineOperandIndexDebugLexicalBlock);
break;
case CommonDebugInfoDebugTypeComposite:
case CommonDebugInfoDebugCompilationUnit:
assert(false &&
"DebugTypeComposite and DebugCompilationUnit are lexical "
"scopes, but we inline functions into a function or a block "
"of a function, not into a struct/class or a global scope.");
break;
default:
assert(false &&
"Unreachable. a debug extension instruction for a "
"lexical scope must be DebugFunction, DebugTypeComposite, "
"DebugLexicalBlock, or DebugCompilationUnit.");
break;
}
} else {
if (line->opcode() == spv::Op::OpLine) {
line_number = line->GetSingleWordOperand(kOpLineOperandLineIndex);
} else if (line->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugLine) {
line_number = line->GetSingleWordOperand(kLineOperandIndexDebugLine);
} else {
assert(false &&
"Unreachable. A line instruction must be OpLine or DebugLine");
}
// If we need the line number as an ID, generate that constant now.
// If Constant or DefUse managers are invalid, generate constant
// directly into the global value section of the module; do not
// use Constant manager which may attempt to invoke building of the
// DefUse manager which cannot be done during inlining. The extra
// constants that may be generated here is likely not significant
// and will likely be cleaned up in later passes.
if (line_number_type == spv_operand_type_t::SPV_OPERAND_TYPE_ID &&
line->opcode() == spv::Op::OpLine) {
if (!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse) ||
!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisConstants))
line_number = AddNewConstInGlobals(context(), line_number);
else
line_number =
context()->get_constant_mgr()->GetUIntConstId(line_number);
}
}
uint32_t result_id = context()->TakeNextId();
std::unique_ptr<Instruction> inlined_at(new Instruction(
context(), spv::Op::OpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
result_id,
{
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {setId}},
{spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
{static_cast<uint32_t>(CommonDebugInfoDebugInlinedAt)}},
{line_number_type, {line_number}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {scope.GetLexicalScope()}},
}));
// |scope| already has DebugInlinedAt. We put the existing DebugInlinedAt
// into the Inlined operand of this new DebugInlinedAt.
if (scope.GetInlinedAt() != kNoInlinedAt) {
inlined_at->AddOperand(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {scope.GetInlinedAt()}});
}
RegisterDbgInst(inlined_at.get());
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(inlined_at.get());
context()->module()->AddExtInstDebugInfo(std::move(inlined_at));
return result_id;
}
DebugScope DebugInfoManager::BuildDebugScope(
const DebugScope& callee_instr_scope,
DebugInlinedAtContext* inlined_at_ctx) {
return DebugScope(callee_instr_scope.GetLexicalScope(),
BuildDebugInlinedAtChain(callee_instr_scope.GetInlinedAt(),
inlined_at_ctx));
}
uint32_t DebugInfoManager::BuildDebugInlinedAtChain(
uint32_t callee_inlined_at, DebugInlinedAtContext* inlined_at_ctx) {
if (inlined_at_ctx->GetScopeOfCallInstruction().GetLexicalScope() ==
kNoDebugScope)
return kNoInlinedAt;
// Reuse the already generated DebugInlinedAt chain if exists.
uint32_t already_generated_chain_head_id =
inlined_at_ctx->GetDebugInlinedAtChain(callee_inlined_at);
if (already_generated_chain_head_id != kNoInlinedAt) {
return already_generated_chain_head_id;
}
const uint32_t new_dbg_inlined_at_id =
CreateDebugInlinedAt(inlined_at_ctx->GetLineOfCallInstruction(),
inlined_at_ctx->GetScopeOfCallInstruction());
if (new_dbg_inlined_at_id == kNoInlinedAt) return kNoInlinedAt;
if (callee_inlined_at == kNoInlinedAt) {
inlined_at_ctx->SetDebugInlinedAtChain(kNoInlinedAt, new_dbg_inlined_at_id);
return new_dbg_inlined_at_id;
}
uint32_t chain_head_id = kNoInlinedAt;
uint32_t chain_iter_id = callee_inlined_at;
Instruction* last_inlined_at_in_chain = nullptr;
do {
Instruction* new_inlined_at_in_chain = CloneDebugInlinedAt(
chain_iter_id, /* insert_before */ last_inlined_at_in_chain);
assert(new_inlined_at_in_chain != nullptr);
// Set DebugInlinedAt of the new scope as the head of the chain.
if (chain_head_id == kNoInlinedAt)
chain_head_id = new_inlined_at_in_chain->result_id();
// Previous DebugInlinedAt of the chain must point to the new
// DebugInlinedAt as its Inlined operand to build a recursive
// chain.
if (last_inlined_at_in_chain != nullptr) {
SetInlinedOperand(last_inlined_at_in_chain,
new_inlined_at_in_chain->result_id());
}
last_inlined_at_in_chain = new_inlined_at_in_chain;
chain_iter_id = GetInlinedOperand(new_inlined_at_in_chain);
} while (chain_iter_id != kNoInlinedAt);
// Put |new_dbg_inlined_at_id| into the end of the chain.
SetInlinedOperand(last_inlined_at_in_chain, new_dbg_inlined_at_id);
// Keep the new chain information that will be reused it.
inlined_at_ctx->SetDebugInlinedAtChain(callee_inlined_at, chain_head_id);
return chain_head_id;
}
Instruction* DebugInfoManager::GetDebugOperationWithDeref() {
if (deref_operation_ != nullptr) return deref_operation_;
uint32_t result_id = context()->TakeNextId();
std::unique_ptr<Instruction> deref_operation;
if (context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) {
deref_operation = std::unique_ptr<Instruction>(new Instruction(
context(), spv::Op::OpExtInst,
context()->get_type_mgr()->GetVoidTypeId(), result_id,
{
{SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
{SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
{static_cast<uint32_t>(OpenCLDebugInfo100DebugOperation)}},
{SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION,
{static_cast<uint32_t>(OpenCLDebugInfo100Deref)}},
}));
} else {
uint32_t deref_id = context()->get_constant_mgr()->GetUIntConstId(
NonSemanticShaderDebugInfo100Deref);
deref_operation = std::unique_ptr<Instruction>(
new Instruction(context(), spv::Op::OpExtInst,
context()->get_type_mgr()->GetVoidTypeId(), result_id,
{
{SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
{SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
{static_cast<uint32_t>(
NonSemanticShaderDebugInfo100DebugOperation)}},
{SPV_OPERAND_TYPE_ID, {deref_id}},
}));
}
// Add to the front of |ext_inst_debuginfo_|.
deref_operation_ =
context()->module()->ext_inst_debuginfo_begin()->InsertBefore(
std::move(deref_operation));
RegisterDbgInst(deref_operation_);
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(deref_operation_);
return deref_operation_;
}
Instruction* DebugInfoManager::DerefDebugExpression(Instruction* dbg_expr) {
assert(dbg_expr->GetCommonDebugOpcode() == CommonDebugInfoDebugExpression);
std::unique_ptr<Instruction> deref_expr(dbg_expr->Clone(context()));
deref_expr->SetResultId(context()->TakeNextId());
deref_expr->InsertOperand(
kDebugExpressOperandOperationIndex,
{SPV_OPERAND_TYPE_ID, {GetDebugOperationWithDeref()->result_id()}});
auto* deref_expr_instr =
context()->ext_inst_debuginfo_end()->InsertBefore(std::move(deref_expr));
AnalyzeDebugInst(deref_expr_instr);
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(deref_expr_instr);
return deref_expr_instr;
}
Instruction* DebugInfoManager::GetDebugInfoNone() {
if (debug_info_none_inst_ != nullptr) return debug_info_none_inst_;
uint32_t result_id = context()->TakeNextId();
std::unique_ptr<Instruction> dbg_info_none_inst(new Instruction(
context(), spv::Op::OpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
result_id,
{
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
{static_cast<uint32_t>(CommonDebugInfoDebugInfoNone)}},
}));
// Add to the front of |ext_inst_debuginfo_|.
debug_info_none_inst_ =
context()->module()->ext_inst_debuginfo_begin()->InsertBefore(
std::move(dbg_info_none_inst));
RegisterDbgInst(debug_info_none_inst_);
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(debug_info_none_inst_);
return debug_info_none_inst_;
}
Instruction* DebugInfoManager::GetEmptyDebugExpression() {
if (empty_debug_expr_inst_ != nullptr) return empty_debug_expr_inst_;
uint32_t result_id = context()->TakeNextId();
std::unique_ptr<Instruction> empty_debug_expr(new Instruction(
context(), spv::Op::OpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
result_id,
{
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
{static_cast<uint32_t>(CommonDebugInfoDebugExpression)}},
}));
// Add to the front of |ext_inst_debuginfo_|.
empty_debug_expr_inst_ =
context()->module()->ext_inst_debuginfo_begin()->InsertBefore(
std::move(empty_debug_expr));
RegisterDbgInst(empty_debug_expr_inst_);
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(empty_debug_expr_inst_);
return empty_debug_expr_inst_;
}
Instruction* DebugInfoManager::GetDebugInlinedAt(uint32_t dbg_inlined_at_id) {
auto* inlined_at = GetDbgInst(dbg_inlined_at_id);
if (inlined_at == nullptr) return nullptr;
if (inlined_at->GetCommonDebugOpcode() != CommonDebugInfoDebugInlinedAt) {
return nullptr;
}
return inlined_at;
}
Instruction* DebugInfoManager::CloneDebugInlinedAt(uint32_t clone_inlined_at_id,
Instruction* insert_before) {
auto* inlined_at = GetDebugInlinedAt(clone_inlined_at_id);
if (inlined_at == nullptr) return nullptr;
std::unique_ptr<Instruction> new_inlined_at(inlined_at->Clone(context()));
new_inlined_at->SetResultId(context()->TakeNextId());
RegisterDbgInst(new_inlined_at.get());
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inlined_at.get());
if (insert_before != nullptr)
return insert_before->InsertBefore(std::move(new_inlined_at));
return context()->module()->ext_inst_debuginfo_end()->InsertBefore(
std::move(new_inlined_at));
}
bool DebugInfoManager::IsVariableDebugDeclared(uint32_t variable_id) {
auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
return dbg_decl_itr != var_id_to_dbg_decl_.end();
}
bool DebugInfoManager::KillDebugDeclares(uint32_t variable_id) {
bool modified = false;
auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
if (dbg_decl_itr != var_id_to_dbg_decl_.end()) {
// We intentionally copy the list of DebugDeclare instructions because
// context()->KillInst(dbg_decl) will update |var_id_to_dbg_decl_|. If we
// directly use |dbg_decl_itr->second|, it accesses a dangling pointer.
auto copy_dbg_decls = dbg_decl_itr->second;
for (auto* dbg_decl : copy_dbg_decls) {
context()->KillInst(dbg_decl);
modified = true;
}
var_id_to_dbg_decl_.erase(dbg_decl_itr);
}
return modified;
}
uint32_t DebugInfoManager::GetParentScope(uint32_t child_scope) {
auto dbg_scope_itr = id_to_dbg_inst_.find(child_scope);
assert(dbg_scope_itr != id_to_dbg_inst_.end());
CommonDebugInfoInstructions debug_opcode =
dbg_scope_itr->second->GetCommonDebugOpcode();
uint32_t parent_scope = kNoDebugScope;
switch (debug_opcode) {
case CommonDebugInfoDebugFunction:
parent_scope = dbg_scope_itr->second->GetSingleWordOperand(
kDebugFunctionOperandParentIndex);
break;
case CommonDebugInfoDebugLexicalBlock:
parent_scope = dbg_scope_itr->second->GetSingleWordOperand(
kDebugLexicalBlockOperandParentIndex);
break;
case CommonDebugInfoDebugTypeComposite:
parent_scope = dbg_scope_itr->second->GetSingleWordOperand(
kDebugTypeCompositeOperandParentIndex);
break;
case CommonDebugInfoDebugCompilationUnit:
// DebugCompilationUnit does not have a parent scope.
break;
default:
assert(false &&
"Unreachable. A debug scope instruction must be "
"DebugFunction, DebugTypeComposite, DebugLexicalBlock, "
"or DebugCompilationUnit.");
break;
}
return parent_scope;
}
bool DebugInfoManager::IsAncestorOfScope(uint32_t scope, uint32_t ancestor) {
uint32_t ancestor_scope_itr = scope;
while (ancestor_scope_itr != kNoDebugScope) {
if (ancestor == ancestor_scope_itr) return true;
ancestor_scope_itr = GetParentScope(ancestor_scope_itr);
}
return false;
}
bool DebugInfoManager::IsDeclareVisibleToInstr(Instruction* dbg_declare,
Instruction* scope) {
assert(dbg_declare != nullptr);
assert(scope != nullptr);
std::vector<uint32_t> scope_ids;
if (scope->opcode() == spv::Op::OpPhi) {
scope_ids.push_back(scope->GetDebugScope().GetLexicalScope());
for (uint32_t i = 0; i < scope->NumInOperands(); i += 2) {
auto* value = context()->get_def_use_mgr()->GetDef(
scope->GetSingleWordInOperand(i));
if (value != nullptr)
scope_ids.push_back(value->GetDebugScope().GetLexicalScope());
}
} else {
scope_ids.push_back(scope->GetDebugScope().GetLexicalScope());
}
uint32_t dbg_local_var_id =
dbg_declare->GetSingleWordOperand(kDebugDeclareOperandLocalVariableIndex);
auto dbg_local_var_itr = id_to_dbg_inst_.find(dbg_local_var_id);
assert(dbg_local_var_itr != id_to_dbg_inst_.end());
uint32_t decl_scope_id = dbg_local_var_itr->second->GetSingleWordOperand(
kDebugLocalVariableOperandParentIndex);
// If the scope of DebugDeclare is an ancestor scope of the instruction's
// scope, the local variable is visible to the instruction.
for (uint32_t scope_id : scope_ids) {
if (scope_id != kNoDebugScope &&
IsAncestorOfScope(scope_id, decl_scope_id)) {
return true;
}
}
return false;
}
bool DebugInfoManager::AddDebugValueForVariable(Instruction* scope_and_line,
uint32_t variable_id,
uint32_t value_id,
Instruction* insert_pos) {
assert(scope_and_line != nullptr);
auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
if (dbg_decl_itr == var_id_to_dbg_decl_.end()) return false;
bool modified = false;
for (auto* dbg_decl_or_val : dbg_decl_itr->second) {
// Avoid inserting the new DebugValue between OpPhi or OpVariable
// instructions.
Instruction* insert_before = insert_pos->NextNode();
while (insert_before->opcode() == spv::Op::OpPhi ||
insert_before->opcode() == spv::Op::OpVariable) {
insert_before = insert_before->NextNode();
}
modified |= AddDebugValueForDecl(dbg_decl_or_val, value_id, insert_before,
scope_and_line) != nullptr;
}
return modified;
}
Instruction* DebugInfoManager::AddDebugValueForDecl(
Instruction* dbg_decl, uint32_t value_id, Instruction* insert_before,
Instruction* scope_and_line) {
if (dbg_decl == nullptr || !IsDebugDeclare(dbg_decl)) return nullptr;
std::unique_ptr<Instruction> dbg_val(dbg_decl->Clone(context()));
dbg_val->SetResultId(context()->TakeNextId());
dbg_val->SetInOperand(kExtInstInstructionInIdx, {CommonDebugInfoDebugValue});
dbg_val->SetOperand(kDebugDeclareOperandVariableIndex, {value_id});
dbg_val->SetOperand(kDebugValueOperandExpressionIndex,
{GetEmptyDebugExpression()->result_id()});
dbg_val->UpdateDebugInfoFrom(scope_and_line);
auto* added_dbg_val = insert_before->InsertBefore(std::move(dbg_val));
AnalyzeDebugInst(added_dbg_val);
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_val);
if (context()->AreAnalysesValid(
IRContext::Analysis::kAnalysisInstrToBlockMapping)) {
auto insert_blk = context()->get_instr_block(insert_before);
context()->set_instr_block(added_dbg_val, insert_blk);
}
return added_dbg_val;
}
uint32_t DebugInfoManager::GetVulkanDebugOperation(Instruction* inst) {
assert(inst->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugOperation &&
"inst must be Vulkan DebugOperation");
return context()
->get_constant_mgr()
->GetConstantFromInst(context()->get_def_use_mgr()->GetDef(
inst->GetSingleWordOperand(kDebugOperationOperandOperationIndex)))
->GetU32();
}
uint32_t DebugInfoManager::GetVariableIdOfDebugValueUsedForDeclare(
Instruction* inst) {
if (inst->GetCommonDebugOpcode() != CommonDebugInfoDebugValue) return 0;
auto* expr =
GetDbgInst(inst->GetSingleWordOperand(kDebugValueOperandExpressionIndex));
if (expr == nullptr) return 0;
if (expr->NumOperands() != kDebugExpressOperandOperationIndex + 1) return 0;
auto* operation = GetDbgInst(
expr->GetSingleWordOperand(kDebugExpressOperandOperationIndex));
if (operation == nullptr) return 0;
// OpenCL.DebugInfo.100 contains a literal for the operation, Vulkan uses an
// OpConstant.
if (inst->IsOpenCL100DebugInstr()) {
if (operation->GetSingleWordOperand(kDebugOperationOperandOperationIndex) !=
OpenCLDebugInfo100Deref) {
return 0;
}
} else {
uint32_t operation_const = GetVulkanDebugOperation(operation);
if (operation_const != NonSemanticShaderDebugInfo100Deref) {
return 0;
}
}
uint32_t var_id =
inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
if (!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) {
assert(false &&
"Checking a DebugValue can be used for declare needs DefUseManager");
return 0;
}
auto* var = context()->get_def_use_mgr()->GetDef(var_id);
if (var->opcode() == spv::Op::OpVariable &&
spv::StorageClass(
var->GetSingleWordOperand(kOpVariableOperandStorageClassIndex)) ==
spv::StorageClass::Function) {
return var_id;
}
return 0;
}
bool DebugInfoManager::IsDebugDeclare(Instruction* instr) {
if (!instr->IsCommonDebugInstr()) return false;
return instr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare ||
GetVariableIdOfDebugValueUsedForDeclare(instr) != 0;
}
void DebugInfoManager::ReplaceAllUsesInDebugScopeWithPredicate(
uint32_t before, uint32_t after,
const std::function<bool(Instruction*)>& predicate) {
auto scope_id_to_users_itr = scope_id_to_users_.find(before);
if (scope_id_to_users_itr != scope_id_to_users_.end()) {
for (Instruction* inst : scope_id_to_users_itr->second) {
if (predicate(inst)) inst->UpdateLexicalScope(after);
}
scope_id_to_users_[after] = scope_id_to_users_itr->second;
scope_id_to_users_.erase(scope_id_to_users_itr);
}
auto inlinedat_id_to_users_itr = inlinedat_id_to_users_.find(before);
if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) {
for (Instruction* inst : inlinedat_id_to_users_itr->second) {
if (predicate(inst)) inst->UpdateDebugInlinedAt(after);
}
inlinedat_id_to_users_[after] = inlinedat_id_to_users_itr->second;
inlinedat_id_to_users_.erase(inlinedat_id_to_users_itr);
}
}
void DebugInfoManager::ClearDebugScopeAndInlinedAtUses(Instruction* inst) {
auto scope_id_to_users_itr = scope_id_to_users_.find(inst->result_id());
if (scope_id_to_users_itr != scope_id_to_users_.end()) {
scope_id_to_users_.erase(scope_id_to_users_itr);
}
auto inlinedat_id_to_users_itr =
inlinedat_id_to_users_.find(inst->result_id());
if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) {
inlinedat_id_to_users_.erase(inlinedat_id_to_users_itr);
}
}
void DebugInfoManager::AnalyzeDebugInst(Instruction* inst) {
if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
auto& users = scope_id_to_users_[inst->GetDebugScope().GetLexicalScope()];
users.insert(inst);
}
if (inst->GetDebugInlinedAt() != kNoInlinedAt) {
auto& users = inlinedat_id_to_users_[inst->GetDebugInlinedAt()];
users.insert(inst);
}
if (!inst->IsCommonDebugInstr()) return;
RegisterDbgInst(inst);
if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction ||
inst->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
RegisterDbgFunction(inst);
}
if (deref_operation_ == nullptr &&
inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugOperation &&
inst->GetSingleWordOperand(kDebugOperationOperandOperationIndex) ==
OpenCLDebugInfo100Deref) {
deref_operation_ = inst;
}
if (deref_operation_ == nullptr &&
inst->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugOperation) {
uint32_t operation_const = GetVulkanDebugOperation(inst);
if (operation_const == NonSemanticShaderDebugInfo100Deref) {
deref_operation_ = inst;
}
}
if (debug_info_none_inst_ == nullptr &&
inst->GetCommonDebugOpcode() == CommonDebugInfoDebugInfoNone) {
debug_info_none_inst_ = inst;
}
if (empty_debug_expr_inst_ == nullptr && IsEmptyDebugExpression(inst)) {
empty_debug_expr_inst_ = inst;
}
if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
uint32_t var_id =
inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
RegisterDbgDeclare(var_id, inst);
}
if (uint32_t var_id = GetVariableIdOfDebugValueUsedForDeclare(inst)) {
RegisterDbgDeclare(var_id, inst);
}
}
void DebugInfoManager::ConvertDebugGlobalToLocalVariable(
Instruction* dbg_global_var, Instruction* local_var) {
if (dbg_global_var->GetCommonDebugOpcode() !=
CommonDebugInfoDebugGlobalVariable) {
return;
}
assert(local_var->opcode() == spv::Op::OpVariable ||
local_var->opcode() == spv::Op::OpFunctionParameter);
// Convert |dbg_global_var| to DebugLocalVariable
// All of the operands up to the scope operand are the same for the type
// instructions. The flag operand needs to move from operand
// kDebugGlobalVariableOperandFlagsIndex to
// kDebugLocalVariableOperandFlagsIndex. No other operands are needed to
// define the DebugLocalVariable.
// Modify the opcode.
dbg_global_var->SetInOperand(kExtInstInstructionInIdx,
{CommonDebugInfoDebugLocalVariable});
// Move the flags operand.
auto flags = dbg_global_var->GetSingleWordOperand(
kDebugGlobalVariableOperandFlagsIndex);
dbg_global_var->SetOperand(kDebugLocalVariableOperandFlagsIndex, {flags});
// Remove the extra operands. Starting at the end to avoid copying too much
// data.
for (uint32_t i = dbg_global_var->NumOperands() - 1;
i > kDebugLocalVariableOperandFlagsIndex; --i) {
dbg_global_var->RemoveOperand(i);
}
// Update the def-use manager.
context()->ForgetUses(dbg_global_var);
context()->AnalyzeUses(dbg_global_var);
// Create a DebugDeclare
std::unique_ptr<Instruction> new_dbg_decl(new Instruction(
context(), spv::Op::OpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
context()->TakeNextId(),
{
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
{static_cast<uint32_t>(CommonDebugInfoDebugDeclare)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{dbg_global_var->result_id()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {local_var->result_id()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{GetEmptyDebugExpression()->result_id()}},
}));
// Must insert after all OpVariables in block
Instruction* insert_before = local_var;
while (insert_before->opcode() == spv::Op::OpVariable)
insert_before = insert_before->NextNode();
auto* added_dbg_decl = insert_before->InsertBefore(std::move(new_dbg_decl));
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_decl);
if (context()->AreAnalysesValid(
IRContext::Analysis::kAnalysisInstrToBlockMapping)) {
auto insert_blk = context()->get_instr_block(local_var);
context()->set_instr_block(added_dbg_decl, insert_blk);
}
}
void DebugInfoManager::AnalyzeDebugInsts(Module& module) {
deref_operation_ = nullptr;
debug_info_none_inst_ = nullptr;
empty_debug_expr_inst_ = nullptr;
module.ForEachInst([this](Instruction* cpi) { AnalyzeDebugInst(cpi); });
// Move |empty_debug_expr_inst_| to the beginning of the debug instruction
// list.
if (empty_debug_expr_inst_ != nullptr &&
empty_debug_expr_inst_->PreviousNode() != nullptr &&
empty_debug_expr_inst_->PreviousNode()->IsCommonDebugInstr()) {
empty_debug_expr_inst_->InsertBefore(
&*context()->module()->ext_inst_debuginfo_begin());
}
// Move |debug_info_none_inst_| to the beginning of the debug instruction
// list.
if (debug_info_none_inst_ != nullptr &&
debug_info_none_inst_->PreviousNode() != nullptr &&
debug_info_none_inst_->PreviousNode()->IsCommonDebugInstr()) {
debug_info_none_inst_->InsertBefore(
&*context()->module()->ext_inst_debuginfo_begin());
}
}
void DebugInfoManager::ClearDebugInfo(Instruction* instr) {
auto scope_id_to_users_itr =
scope_id_to_users_.find(instr->GetDebugScope().GetLexicalScope());
if (scope_id_to_users_itr != scope_id_to_users_.end()) {
scope_id_to_users_itr->second.erase(instr);
}
auto inlinedat_id_to_users_itr =
inlinedat_id_to_users_.find(instr->GetDebugInlinedAt());
if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) {
inlinedat_id_to_users_itr->second.erase(instr);
}
if (instr == nullptr || !instr->IsCommonDebugInstr()) {
return;
}
id_to_dbg_inst_.erase(instr->result_id());
if (instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) {
auto fn_id =
instr->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex);
fn_id_to_dbg_fn_.erase(fn_id);
}
if (instr->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
auto fn_id = instr->GetSingleWordOperand(
kDebugFunctionDefinitionOperandOpFunctionIndex);
fn_id_to_dbg_fn_.erase(fn_id);
}
if (instr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare ||
instr->GetCommonDebugOpcode() == CommonDebugInfoDebugValue) {
auto var_or_value_id =
instr->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
auto dbg_decl_itr = var_id_to_dbg_decl_.find(var_or_value_id);
if (dbg_decl_itr != var_id_to_dbg_decl_.end()) {
dbg_decl_itr->second.erase(instr);
}
}
if (deref_operation_ == instr) {
deref_operation_ = nullptr;
for (auto dbg_instr_itr = context()->module()->ext_inst_debuginfo_begin();
dbg_instr_itr != context()->module()->ext_inst_debuginfo_end();
++dbg_instr_itr) {
// OpenCL.DebugInfo.100 contains the operation as a literal operand, in
// Vulkan it's referenced as an OpConstant.
if (instr != &*dbg_instr_itr &&
dbg_instr_itr->GetOpenCL100DebugOpcode() ==
OpenCLDebugInfo100DebugOperation &&
dbg_instr_itr->GetSingleWordOperand(
kDebugOperationOperandOperationIndex) ==
OpenCLDebugInfo100Deref) {
deref_operation_ = &*dbg_instr_itr;
break;
} else if (instr != &*dbg_instr_itr &&
dbg_instr_itr->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugOperation) {
uint32_t operation_const = GetVulkanDebugOperation(&*dbg_instr_itr);
if (operation_const == NonSemanticShaderDebugInfo100Deref) {
deref_operation_ = &*dbg_instr_itr;
break;
}
}
}
}
if (debug_info_none_inst_ == instr) {
debug_info_none_inst_ = nullptr;
for (auto dbg_instr_itr = context()->module()->ext_inst_debuginfo_begin();
dbg_instr_itr != context()->module()->ext_inst_debuginfo_end();
++dbg_instr_itr) {
if (instr != &*dbg_instr_itr && dbg_instr_itr->GetCommonDebugOpcode() ==
CommonDebugInfoDebugInfoNone) {
debug_info_none_inst_ = &*dbg_instr_itr;
break;
}
}
}
if (empty_debug_expr_inst_ == instr) {
empty_debug_expr_inst_ = nullptr;
for (auto dbg_instr_itr = context()->module()->ext_inst_debuginfo_begin();
dbg_instr_itr != context()->module()->ext_inst_debuginfo_end();
++dbg_instr_itr) {
if (instr != &*dbg_instr_itr && IsEmptyDebugExpression(&*dbg_instr_itr)) {
empty_debug_expr_inst_ = &*dbg_instr_itr;
break;
}
}
}
}
} // namespace analysis
} // namespace opt
} // namespace spvtools