Add DebugValue for invisible store in single_store_elim (#4002)

The front-end language compiler would simply emit DebugDeclare for
a variable when it is declared, which is effective through the variable's
scope. Since DebugDeclare only maps an OpVariable to a local variable,
the information can be removed when an optimization pass uses the
loaded value of the variable. DebugValue can be used to specify the
value of a variable. For each value update or phi instruction of a variable,
we can add DebugValue to help debugger inspect the variable at any
point of the program execution.
For example,

float a = 3;
... (complicated cfg) ...
foo(a); // <-- variable inspection: debugger can find DebugValue of `float a` in the nearest dominant

For the code with complicated CFG e.g., for-loop, if-statement, we
need help of ssa-rewrite to analyze the effective value of each variable
in each basic block.

If the value update of the variable happens only once and it dominates
all its uses, local-single-store-elim pass conducts the same value update
with ssa-rewrite and we have to let it add DebugValue for the value assignment.

One main issue is that we have to add DebugValue only when the value
update of a variable is visible to DebugDeclare. For example,

```
{  // scope1
   %stack = OpVariable %ptr_int %int_3
   {  // scope2
       DebugDeclare %foo %stack    <-- local variable "foo" in high-level language source code is declared as OpVariable "%stack"
       // add DebugValue "foo = 3"
       ...
       Store %stack %int_7   <-- foo = 7, add DebugValue "foo = 7"
       ...
       // debugger can inspect the value of "foo"
    }
    Store %stack %int_11   <-- out of "scope2" i.e., scope of "foo". DO NOT add DebugValue "foo = 11"
}
```

However, the initalization of a variable is an exception.
For example, an argument passing of an inlined function must be done out of
the function's scope, but we must add a DebugValue for it.

```
// in HLSL
bar(float arg) { ... }
...
float foo = 3;
bar(foo);

// in SPIR-V
%arg = OpVariable
OpStore %arg %foo   <-- Argument passing. Out of "float arg" scope, but we must add DebugValue for "float arg"
... body of function bar(float arg) ...
```

This PR handles the except case in local-single-store-elim pass. It adds
DebugValue for a store that is considered as an initialization.

The same exception handling code for ssa-rewrite is done by this commit: df4198e50e.
This commit is contained in:
Jaebaek Seo 2020-11-04 13:43:59 -05:00 committed by GitHub
parent c74d5523bb
commit c2b2b57885
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 283 additions and 16 deletions

View File

@ -384,7 +384,8 @@ bool DebugInfoManager::IsVariableDebugDeclared(uint32_t variable_id) {
return dbg_decl_itr != var_id_to_dbg_decl_.end();
}
void DebugInfoManager::KillDebugDeclares(uint32_t variable_id) {
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
@ -394,9 +395,11 @@ void DebugInfoManager::KillDebugDeclares(uint32_t variable_id) {
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) {
@ -514,16 +517,18 @@ Instruction* DebugInfoManager::AddDebugValueWithIndex(
return added_dbg_value;
}
void DebugInfoManager::AddDebugValueIfVarDeclIsVisible(
bool DebugInfoManager::AddDebugValueIfVarDeclIsVisible(
Instruction* scope_and_line, uint32_t variable_id, uint32_t value_id,
Instruction* insert_pos,
std::unordered_set<Instruction*>* invisible_decls) {
auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
if (dbg_decl_itr == var_id_to_dbg_decl_.end()) return;
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) {
if (scope_and_line &&
!IsDeclareVisibleToInstr(dbg_decl_or_val, scope_and_line)) {
if (!IsDeclareVisibleToInstr(dbg_decl_or_val, scope_and_line)) {
if (invisible_decls) invisible_decls->insert(dbg_decl_or_val);
continue;
}
@ -547,10 +552,11 @@ void DebugInfoManager::AddDebugValueIfVarDeclIsVisible(
kDebugValueOperandLocalVariableIndex),
value_id, 0, index_id, insert_before);
assert(added_dbg_value != nullptr);
added_dbg_value->UpdateDebugInfoFrom(scope_and_line ? scope_and_line
: dbg_decl_or_val);
added_dbg_value->UpdateDebugInfoFrom(scope_and_line);
AnalyzeDebugInst(added_dbg_value);
modified = true;
}
return modified;
}
bool DebugInfoManager::AddDebugValueForDecl(Instruction* dbg_decl,

View File

@ -138,14 +138,15 @@ class DebugInfoManager {
bool IsVariableDebugDeclared(uint32_t variable_id);
// Kills all debug declaration instructions with Deref whose 'Local Variable'
// operand is |variable_id|.
void KillDebugDeclares(uint32_t variable_id);
// operand is |variable_id|. Returns whether it kills an instruction or not.
bool KillDebugDeclares(uint32_t variable_id);
// Generates a DebugValue instruction with value |value_id| for every local
// variable that is in the scope of |scope_and_line| and whose memory is
// |variable_id| and inserts it after the instruction |insert_pos|.
// |invisible_decls| returns DebugDeclares invisible to |scope_and_line|.
void AddDebugValueIfVarDeclIsVisible(
// Returns whether a DebugValue is added or not. |invisible_decls| returns
// DebugDeclares invisible to |scope_and_line|.
bool AddDebugValueIfVarDeclIsVisible(
Instruction* scope_and_line, uint32_t variable_id, uint32_t value_id,
Instruction* insert_pos,
std::unordered_set<Instruction*>* invisible_decls);

View File

@ -148,16 +148,41 @@ bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {
context()->get_type_mgr()->GetType(var_inst->type_id());
const analysis::Type* store_type = var_type->AsPointer()->pointee_type();
if (!(store_type->AsStruct() || store_type->AsArray())) {
context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
nullptr, var_id, store_inst->GetSingleWordInOperand(1), store_inst,
nullptr);
context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
modified |= RewriteDebugDeclares(store_inst, var_id);
}
}
return modified;
}
bool LocalSingleStoreElimPass::RewriteDebugDeclares(Instruction* store_inst,
uint32_t var_id) {
std::unordered_set<Instruction*> invisible_decls;
uint32_t value_id = store_inst->GetSingleWordInOperand(1);
bool modified =
context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
store_inst, var_id, value_id, store_inst, &invisible_decls);
// For cases like the argument passing for an inlined function, the value
// assignment is out of DebugDeclare's scope, but we have to preserve the
// value assignment information using DebugValue. Generally, we need
// ssa-rewrite analysis to decide a proper value assignment but at this point
// we confirm that |var_id| has a single store. We can safely add DebugValue.
if (!invisible_decls.empty()) {
BasicBlock* store_block = context()->get_instr_block(store_inst);
DominatorAnalysis* dominator_analysis =
context()->GetDominatorAnalysis(store_block->GetParent());
for (auto* decl : invisible_decls) {
if (dominator_analysis->Dominates(store_inst, decl)) {
context()->get_debug_info_mgr()->AddDebugValueForDecl(decl, value_id);
modified = true;
}
}
}
modified |= context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
return modified;
}
Instruction* LocalSingleStoreElimPass::FindSingleStoreAndCheckUses(
Instruction* var_inst, const std::vector<Instruction*>& users) const {
// Make sure there is exactly 1 store.

View File

@ -94,6 +94,10 @@ class LocalSingleStoreElimPass : public Pass {
bool RewriteLoads(Instruction* store_inst,
const std::vector<Instruction*>& uses, bool* all_rewritten);
// Replaces DebugDeclares of |var_id| with DebugValues using the value
// assignment of |store_inst|.
bool RewriteDebugDeclares(Instruction* store_inst, uint32_t var_id);
// Extensions supported by this pass.
std::unordered_set<std::string> extensions_allowlist_;
};

View File

@ -1211,6 +1211,237 @@ TEST_F(LocalSingleStoreElimTest, DebugValueTest) {
SinglePassRunAndMatch<LocalSingleStoreElimPass>(text, false);
}
TEST_F(LocalSingleStoreElimTest, UseStoreLineInfoForDebugValueLine) {
// When the store is in the scope of OpenCL.DebugInfo.100 DebugDeclare,
// the OpLine of the added OpenCL.DebugInfo.100 DebugValue must be the
// same with the OpLine of the store.
const std::string text = R"(
OpCapability Shader
%1 = OpExtInstImport "OpenCL.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main" %in_var_POSITION %in_var_COLOR %gl_Position %out_var_COLOR
%7 = OpString "simple.hlsl"
%8 = OpString "float"
%9 = OpString "VS_OUTPUT"
%10 = OpString "color"
%11 = OpString "pos"
%12 = OpString "main"
%13 = OpString ""
%14 = OpString "vout"
OpName %in_var_POSITION "in.var.POSITION"
OpName %in_var_COLOR "in.var.COLOR"
OpName %out_var_COLOR "out.var.COLOR"
OpName %main "main"
OpName %VS_OUTPUT "VS_OUTPUT"
OpMemberName %VS_OUTPUT 0 "pos"
OpMemberName %VS_OUTPUT 1 "color"
OpDecorate %gl_Position BuiltIn Position
OpDecorate %in_var_POSITION Location 0
OpDecorate %in_var_COLOR Location 1
OpDecorate %out_var_COLOR Location 0
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%uint = OpTypeInt 32 0
%uint_32 = OpConstant %uint 32
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Input_v4float = OpTypePointer Input %v4float
%_ptr_Output_v4float = OpTypePointer Output %v4float
%void = OpTypeVoid
%uint_256 = OpConstant %uint 256
%uint_128 = OpConstant %uint 128
%uint_0 = OpConstant %uint 0
%36 = OpTypeFunction %void
%_ptr_Function_v4float = OpTypePointer Function %v4float
%VS_OUTPUT = OpTypeStruct %v4float %v4float
%_ptr_Function_VS_OUTPUT = OpTypePointer Function %VS_OUTPUT
%in_var_POSITION = OpVariable %_ptr_Input_v4float Input
%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
%gl_Position = OpVariable %_ptr_Output_v4float Output
%out_var_COLOR = OpVariable %_ptr_Output_v4float Output
%85 = OpExtInst %void %1 DebugOperation Deref
%81 = OpExtInst %void %1 DebugInfoNone
%52 = OpExtInst %void %1 DebugExpression
%40 = OpExtInst %void %1 DebugTypeBasic %8 %uint_32 Float
%41 = OpExtInst %void %1 DebugTypeVector %40 4
%42 = OpExtInst %void %1 DebugSource %7
%43 = OpExtInst %void %1 DebugCompilationUnit 1 4 %42 HLSL
%44 = OpExtInst %void %1 DebugTypeComposite %9 Structure %42 1 8 %43 %9 %uint_256 FlagIsProtected|FlagIsPrivate %45 %46
%46 = OpExtInst %void %1 DebugTypeMember %10 %41 %42 3 10 %44 %uint_128 %uint_128 FlagIsProtected|FlagIsPrivate
%45 = OpExtInst %void %1 DebugTypeMember %11 %41 %42 2 10 %44 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
%47 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %44 %41 %41
%48 = OpExtInst %void %1 DebugFunction %12 %47 %42 6 1 %43 %13 FlagIsProtected|FlagIsPrivate 7 %81
%49 = OpExtInst %void %1 DebugLexicalBlock %42 7 38 %48
%50 = OpExtInst %void %1 DebugLocalVariable %14 %44 %42 8 13 %49 FlagIsLocal
%84 = OpExtInst %void %1 DebugExpression %85
%main = OpFunction %void None %36
%54 = OpLabel
%91 = OpExtInst %void %1 DebugScope %49
OpLine %7 7 23
%83 = OpVariable %_ptr_Function_v4float Function
OpLine %7 8 13
%87 = OpExtInst %void %1 DebugValue %50 %83 %84 %int_1
OpLine %7 7 23
%82 = OpVariable %_ptr_Function_v4float Function
OpLine %7 8 13
%86 = OpExtInst %void %1 DebugValue %50 %82 %84 %int_0
OpNoLine
%92 = OpExtInst %void %1 DebugNoScope
%55 = OpLoad %v4float %in_var_POSITION
%56 = OpLoad %v4float %in_var_COLOR
;CHECK: [[pos:%\w+]] = OpLoad %v4float %in_var_POSITION
;CHECK: [[color:%\w+]] = OpLoad %v4float %in_var_COLOR
%94 = OpExtInst %void %1 DebugScope %49
OpLine %7 9 3
OpStore %82 %55
;CHECK: OpLine [[file:%\w+]] 9 3
;CHECK: OpStore {{%\w+}} [[pos]]
;CHECK: {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[vout:%\w+]] [[pos]] [[empty_expr:%\w+]] %int_0
;CHECK: OpLine [[file]] 10 3
;CHECK: OpStore {{%\w+}} [[color]]
;CHECK: {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[vout]] [[color]] [[empty_expr]] %int_1
OpLine %7 10 3
OpStore %83 %56
OpLine %7 11 10
%90 = OpCompositeConstruct %VS_OUTPUT %55 %56
OpNoLine
%95 = OpExtInst %void %1 DebugNoScope
%58 = OpCompositeExtract %v4float %90 0
OpStore %gl_Position %58
%59 = OpCompositeExtract %v4float %90 1
OpStore %out_var_COLOR %59
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<LocalSingleStoreElimPass>(text, false);
}
TEST_F(LocalSingleStoreElimTest, AddDebugValueforStoreOutOfDebugDeclareScope) {
const std::string text = R"(
OpCapability Shader
%1 = OpExtInstImport "OpenCL.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main" %in_var_POSITION %in_var_COLOR %gl_Position %out_var_COLOR
%7 = OpString "simple.hlsl"
%8 = OpString "float"
%9 = OpString "VS_OUTPUT"
%10 = OpString "color"
%11 = OpString "pos"
%12 = OpString "main"
%13 = OpString ""
%14 = OpString "vout"
OpName %in_var_POSITION "in.var.POSITION"
OpName %in_var_COLOR "in.var.COLOR"
OpName %out_var_COLOR "out.var.COLOR"
OpName %main "main"
OpName %VS_OUTPUT "VS_OUTPUT"
OpMemberName %VS_OUTPUT 0 "pos"
OpMemberName %VS_OUTPUT 1 "color"
OpDecorate %gl_Position BuiltIn Position
OpDecorate %in_var_POSITION Location 0
OpDecorate %in_var_COLOR Location 1
OpDecorate %out_var_COLOR Location 0
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%uint = OpTypeInt 32 0
%uint_32 = OpConstant %uint 32
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Input_v4float = OpTypePointer Input %v4float
%_ptr_Output_v4float = OpTypePointer Output %v4float
%void = OpTypeVoid
%uint_256 = OpConstant %uint 256
%uint_128 = OpConstant %uint 128
%uint_0 = OpConstant %uint 0
%36 = OpTypeFunction %void
%_ptr_Function_v4float = OpTypePointer Function %v4float
%VS_OUTPUT = OpTypeStruct %v4float %v4float
%_ptr_Function_VS_OUTPUT = OpTypePointer Function %VS_OUTPUT
%in_var_POSITION = OpVariable %_ptr_Input_v4float Input
%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
%gl_Position = OpVariable %_ptr_Output_v4float Output
%out_var_COLOR = OpVariable %_ptr_Output_v4float Output
%85 = OpExtInst %void %1 DebugOperation Deref
%81 = OpExtInst %void %1 DebugInfoNone
%52 = OpExtInst %void %1 DebugExpression
%40 = OpExtInst %void %1 DebugTypeBasic %8 %uint_32 Float
%41 = OpExtInst %void %1 DebugTypeVector %40 4
%42 = OpExtInst %void %1 DebugSource %7
%43 = OpExtInst %void %1 DebugCompilationUnit 1 4 %42 HLSL
%44 = OpExtInst %void %1 DebugTypeComposite %9 Structure %42 1 8 %43 %9 %uint_256 FlagIsProtected|FlagIsPrivate %45 %46
%46 = OpExtInst %void %1 DebugTypeMember %10 %41 %42 3 10 %44 %uint_128 %uint_128 FlagIsProtected|FlagIsPrivate
%45 = OpExtInst %void %1 DebugTypeMember %11 %41 %42 2 10 %44 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
%47 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %44 %41 %41
%48 = OpExtInst %void %1 DebugFunction %12 %47 %42 6 1 %43 %13 FlagIsProtected|FlagIsPrivate 7 %81
%49 = OpExtInst %void %1 DebugLexicalBlock %42 7 38 %48
%50 = OpExtInst %void %1 DebugLocalVariable %14 %44 %42 8 13 %49 FlagIsLocal
%51 = OpExtInst %void %1 DebugLocalVariable %10 %41 %42 7 23 %48 FlagIsLocal 2
%53 = OpExtInst %void %1 DebugLocalVariable %11 %41 %42 6 23 %48 FlagIsLocal 1
;CHECK: [[dbg_color:%\w+]] = OpExtInst %void {{%\w+}} DebugLocalVariable {{%\w+}} {{%\w+}} {{%\w+}} 7 23 {{%\w+}} FlagIsLocal 2
;CHECK: [[dbg_pos:%\w+]] = OpExtInst %void {{%\w+}} DebugLocalVariable {{%\w+}} {{%\w+}} {{%\w+}} 6 23 {{%\w+}} FlagIsLocal 1
%84 = OpExtInst %void %1 DebugExpression %85
%main = OpFunction %void None %36
%54 = OpLabel
%91 = OpExtInst %void %1 DebugScope %49
OpLine %7 7 23
%83 = OpVariable %_ptr_Function_v4float Function
OpLine %7 8 13
%87 = OpExtInst %void %1 DebugValue %50 %83 %84 %int_1
OpLine %7 7 23
%82 = OpVariable %_ptr_Function_v4float Function
OpLine %7 8 13
%86 = OpExtInst %void %1 DebugValue %50 %82 %84 %int_0
OpNoLine
%92 = OpExtInst %void %1 DebugNoScope
%param_var_pos = OpVariable %_ptr_Function_v4float Function
%param_var_color = OpVariable %_ptr_Function_v4float Function
%55 = OpLoad %v4float %in_var_POSITION
OpStore %param_var_pos %55
%56 = OpLoad %v4float %in_var_COLOR
;CHECK: DebugNoScope
;CHECK-NOT: OpLine
;CHECK: [[pos:%\w+]] = OpLoad %v4float %in_var_POSITION
;CHECK: [[color:%\w+]] = OpLoad %v4float %in_var_COLOR
OpStore %param_var_color %56
%93 = OpExtInst %void %1 DebugScope %48
OpLine %7 6 23
%73 = OpExtInst %void %1 DebugDeclare %53 %param_var_pos %52
OpLine %7 7 23
%74 = OpExtInst %void %1 DebugDeclare %51 %param_var_color %52
;CHECK: OpLine [[file:%\w+]] 6 23
;CHECK-NEXT: {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[dbg_pos]] [[pos]] [[empty_expr:%\w+]]
;CHECK: OpLine [[file]] 7 23
;CHECK-NEXT: {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[dbg_color]] [[color]] [[empty_expr]]
%94 = OpExtInst %void %1 DebugScope %49
OpLine %7 9 3
OpStore %82 %55
OpLine %7 10 3
OpStore %83 %56
OpLine %7 11 10
%90 = OpCompositeConstruct %VS_OUTPUT %55 %56
OpNoLine
%95 = OpExtInst %void %1 DebugNoScope
%58 = OpCompositeExtract %v4float %90 0
OpStore %gl_Position %58
%59 = OpCompositeExtract %v4float %90 1
OpStore %out_var_COLOR %59
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<LocalSingleStoreElimPass>(text, false);
}
// TODO(greg-lunarg): Add tests to verify handling of these cases:
//
// Other types