spirv-opt: properly preserve DebugValue indexes operand (#4022)

spirv-opt has a bug that `DebugInfoManager::AddDebugValueWithIndex()` does not
preserve `Indexes` operands of
[DebugValue](https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugValue).
It has to preserve all of those `Indexes` operands, but it preserves only the first index
operand.

This PR removes `DebugInfoManager::AddDebugValueWithIndex()` and lets the spirv-opt
use `DebugInfoManager::AddDebugValueForDecl()`.
`DebugInfoManager::AddDebugValueForDecl()` preserves the Indexes operand correctly.
This commit is contained in:
Jaebaek Seo 2020-11-13 12:06:38 -05:00 committed by GitHub
parent 1cda495274
commit f686518cee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 297 additions and 87 deletions

View File

@ -31,9 +31,7 @@ static const uint32_t kDebugInlinedAtOperandInlinedIndex = 6;
static const uint32_t kDebugExpressOperandOperationIndex = 4;
static const uint32_t kDebugDeclareOperandLocalVariableIndex = 4;
static const uint32_t kDebugDeclareOperandVariableIndex = 5;
static const uint32_t kDebugValueOperandLocalVariableIndex = 4;
static const uint32_t kDebugValueOperandExpressionIndex = 6;
static const uint32_t kDebugValueOperandIndexesIndex = 7;
static const uint32_t kDebugOperationOperandOperationIndex = 4;
static const uint32_t kOpVariableOperandStorageClassIndex = 2;
static const uint32_t kDebugLocalVariableOperandParentIndex = 9;
@ -479,44 +477,6 @@ bool DebugInfoManager::IsDeclareVisibleToInstr(Instruction* dbg_declare,
return false;
}
Instruction* DebugInfoManager::AddDebugValueWithIndex(
uint32_t dbg_local_var_id, uint32_t value_id, uint32_t expr_id,
uint32_t index_id, Instruction* insert_before) {
uint32_t result_id = context()->TakeNextId();
if (!result_id) return nullptr;
std::unique_ptr<Instruction> new_dbg_value(new Instruction(
context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
result_id,
{
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{context()
->get_feature_mgr()
->GetExtInstImportId_OpenCL100DebugInfo()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
{static_cast<uint32_t>(OpenCLDebugInfo100DebugValue)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {dbg_local_var_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {value_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{expr_id == 0 ? GetEmptyDebugExpression()->result_id() : expr_id}},
}));
if (index_id) {
new_dbg_value->AddOperand(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {index_id}});
}
Instruction* added_dbg_value =
insert_before->InsertBefore(std::move(new_dbg_value));
AnalyzeDebugInst(added_dbg_value);
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_value);
if (context()->AreAnalysesValid(
IRContext::Analysis::kAnalysisInstrToBlockMapping)) {
auto insert_blk = context()->get_instr_block(insert_before);
context()->set_instr_block(added_dbg_value, insert_blk);
}
return added_dbg_value;
}
bool DebugInfoManager::AddDebugValueIfVarDeclIsVisible(
Instruction* scope_and_line, uint32_t variable_id, uint32_t value_id,
Instruction* insert_pos,
@ -540,28 +500,15 @@ bool DebugInfoManager::AddDebugValueIfVarDeclIsVisible(
insert_before->opcode() == SpvOpVariable) {
insert_before = insert_before->NextNode();
}
uint32_t index_id = 0;
if (dbg_decl_or_val->NumOperands() > kDebugValueOperandIndexesIndex) {
index_id =
dbg_decl_or_val->GetSingleWordOperand(kDebugValueOperandIndexesIndex);
}
Instruction* added_dbg_value =
AddDebugValueWithIndex(dbg_decl_or_val->GetSingleWordOperand(
kDebugValueOperandLocalVariableIndex),
value_id, 0, index_id, insert_before);
assert(added_dbg_value != nullptr);
added_dbg_value->UpdateDebugInfoFrom(scope_and_line);
AnalyzeDebugInst(added_dbg_value);
modified = true;
modified |= AddDebugValueForDecl(dbg_decl_or_val, value_id,
insert_before) != nullptr;
}
return modified;
}
bool DebugInfoManager::AddDebugValueForDecl(Instruction* dbg_decl,
uint32_t value_id) {
if (dbg_decl == nullptr || !IsDebugDeclare(dbg_decl)) return false;
Instruction* DebugInfoManager::AddDebugValueForDecl(
Instruction* dbg_decl, uint32_t value_id, Instruction* insert_before) {
if (dbg_decl == nullptr || !IsDebugDeclare(dbg_decl)) return nullptr;
std::unique_ptr<Instruction> dbg_val(dbg_decl->Clone(context()));
dbg_val->SetResultId(context()->TakeNextId());
@ -571,16 +518,16 @@ bool DebugInfoManager::AddDebugValueForDecl(Instruction* dbg_decl,
dbg_val->SetOperand(kDebugValueOperandExpressionIndex,
{GetEmptyDebugExpression()->result_id()});
auto* added_dbg_val = dbg_decl->InsertBefore(std::move(dbg_val));
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(dbg_decl);
auto insert_blk = context()->get_instr_block(insert_before);
context()->set_instr_block(added_dbg_val, insert_blk);
}
return true;
return added_dbg_val;
}
uint32_t DebugInfoManager::GetVariableIdOfDebugValueUsedForDeclare(

View File

@ -151,18 +151,13 @@ class DebugInfoManager {
Instruction* insert_pos,
std::unordered_set<Instruction*>* invisible_decls);
// Generates a DebugValue instruction with |dbg_local_var_id|, |value_id|,
// |expr_id|, |index_id| operands and inserts it before |insert_before|.
Instruction* AddDebugValueWithIndex(uint32_t dbg_local_var_id,
uint32_t value_id, uint32_t expr_id,
uint32_t index_id,
// Creates a DebugValue for DebugDeclare |dbg_decl| and inserts it before
// |insert_before|. The new DebugValue has the same line, scope, and
// operands with DebugDeclare but it uses |value_id| for value. Returns
// the added DebugValue, or nullptr if it does not add a DebugValue.
Instruction* AddDebugValueForDecl(Instruction* dbg_decl, uint32_t value_id,
Instruction* insert_before);
// Adds DebugValue for DebugDeclare |dbg_decl|. The new DebugValue has the
// same line, scope, and operands but it uses |value_id| for value. Returns
// weather it succeeds or not.
bool AddDebugValueForDecl(Instruction* dbg_decl, uint32_t value_id);
// Erases |instr| from data structures of this class.
void ClearDebugInfo(Instruction* instr);

View File

@ -174,7 +174,8 @@ bool LocalSingleStoreElimPass::RewriteDebugDeclares(Instruction* store_inst,
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);
context()->get_debug_info_mgr()->AddDebugValueForDecl(decl, value_id,
decl);
modified = true;
}
}

View File

@ -25,7 +25,6 @@
#include "source/opt/types.h"
#include "source/util/make_unique.h"
static const uint32_t kDebugDeclareOperandLocalVariableIndex = 4;
static const uint32_t kDebugValueOperandValueIndex = 5;
static const uint32_t kDebugValueOperandExpressionIndex = 6;
@ -173,17 +172,19 @@ bool ScalarReplacementPass::ReplaceWholeDebugDeclare(
// Add DebugValue instruction with Indexes operand and Deref operation.
int32_t idx = 0;
for (const auto* var : replacements) {
uint32_t dbg_local_variable =
dbg_decl->GetSingleWordOperand(kDebugDeclareOperandLocalVariableIndex);
uint32_t index_id = context()->get_constant_mgr()->GetSIntConst(idx);
Instruction* added_dbg_value =
context()->get_debug_info_mgr()->AddDebugValueWithIndex(
dbg_local_variable,
/*value_id=*/var->result_id(), /*expr_id=*/deref_expr->result_id(),
index_id, /*insert_before=*/var->NextNode());
context()->get_debug_info_mgr()->AddDebugValueForDecl(
dbg_decl, /*value_id=*/var->result_id(),
/*insert_before=*/var->NextNode());
if (added_dbg_value == nullptr) return false;
added_dbg_value->UpdateDebugInfoFrom(dbg_decl);
added_dbg_value->AddOperand(
{SPV_OPERAND_TYPE_ID,
{context()->get_constant_mgr()->GetSIntConst(idx)}});
added_dbg_value->SetOperand(kDebugValueOperandExpressionIndex,
{deref_expr->result_id()});
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) {
context()->get_def_use_mgr()->AnalyzeInstUse(added_dbg_value);
}
++idx;
}
return true;

View File

@ -680,8 +680,8 @@ Pass::Status SSARewriter::AddDebugValuesForInvisibleDebugDecls(Function* fp) {
// If |value| dominates |decl|, we can set it as DebugValue.
if (value && (pass_->context()->get_instr_block(value) == nullptr ||
dom_tree->Dominates(value, decl))) {
if (!pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
decl, value->result_id())) {
if (pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
decl, value->result_id(), decl) == nullptr) {
return Pass::Status::Failure;
}
} else {
@ -689,8 +689,8 @@ Pass::Status SSARewriter::AddDebugValuesForInvisibleDebugDecls(Function* fp) {
// assign the value in the immediate dominator.
value_id = GetValueAtBlock(var_id, dom_tree->ImmediateDominator(bb));
if (value_id &&
!pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
decl, value_id)) {
pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
decl, value_id, decl) == nullptr) {
return Pass::Status::Failure;
}
}

View File

@ -2371,6 +2371,154 @@ TEST_F(LocalSSAElimTest, AddDebugValueForFunctionParameterWithPhi) {
SinglePassRunAndMatch<SSARewritePass>(text, true);
}
TEST_F(LocalSSAElimTest, DebugValueWithIndexesInForLoop) {
// #version 140
//
// in vec4 BC;
// out float fo;
//
// struct T {
// float a;
// float f;
// };
//
// struct value {
// int x;
// int y;
// T z;
// };
//
// void main()
// {
// value v;
// v.z.f = 0.0;
// for (int i=0; i<4; i++) {
// v.z.f = v.z.f + BC[i];
// }
// fo = v.z.f;
// }
const std::string text = R"(
; CHECK: [[f_name:%\w+]] = OpString "f"
; CHECK: [[dbg_f:%\w+]] = OpExtInst %void [[ext:%\d+]] DebugLocalVariable [[f_name]]
; CHECK: OpStore %f %float_0
; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] %float_0 [[null_expr:%\d+]] %int_2 %int_1
; CHECK-NOT: DebugDeclare
; CHECK: [[loop_head:%\w+]] = OpLabel
; CHECK: [[phi0:%\w+]] = OpPhi %float %float_0
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_f]] [[phi0]] [[null_expr]] %int_2 %int_1
; CHECK: OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]] None
; CHECK-NEXT: OpBranch [[loop_body:%\w+]]
; CHECK-NEXT: [[loop_body]] = OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[bb:%\w+]] [[loop_merge]]
; CHECK: [[bb]] = OpLabel
; CHECK: OpStore %f [[f_val:%\w+]]
; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] [[f_val]] [[null_expr]] %int_2 %int_1
; CHECK-NEXT: OpBranch [[loop_cont]]
; CHECK: [[loop_cont]] = OpLabel
; CHECK: OpBranch [[loop_head]]
; CHECK: [[loop_merge]] = OpLabel
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
%ext = OpExtInstImport "OpenCL.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %BC %fo
OpExecutionMode %main OriginUpperLeft
%file_name = OpString "test"
OpSource GLSL 140
%float_name = OpString "float"
%main_name = OpString "main"
%f_name = OpString "f"
%i_name = OpString "i"
OpName %main "main"
OpName %f "f"
OpName %i "i"
OpName %BC "BC"
OpName %fo "fo"
%void = OpTypeVoid
%8 = OpTypeFunction %void
%float = OpTypeFloat 32
%_ptr_Function_float = OpTypePointer Function %float
%float_0 = OpConstant %float 0
%int = OpTypeInt 32 1
%uint = OpTypeInt 32 0
%uint_32 = OpConstant %uint 32
%_ptr_Function_int = OpTypePointer Function %int
%int_0 = OpConstant %int 0
%int_4 = OpConstant %int 4
%bool = OpTypeBool
%v4float = OpTypeVector %float 4
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BC = OpVariable %_ptr_Input_v4float Input
%_ptr_Input_float = OpTypePointer Input %float
%int_1 = OpConstant %int 1
%int_2 = OpConstant %int 2
%_ptr_Output_float = OpTypePointer Output %float
%fo = OpVariable %_ptr_Output_float Output
%deref = OpExtInst %void %ext DebugOperation Deref
%null_expr = OpExtInst %void %ext DebugExpression
%deref_expr = OpExtInst %void %ext DebugExpression %deref
%src = OpExtInst %void %ext DebugSource %file_name
%cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL
%dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 Float
%dbg_v4f = OpExtInst %void %ext DebugTypeVector %dbg_tf 4
%main_ty = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %dbg_v4f %dbg_v4f
%dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 10 %main
%dbg_f = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_v4f %src 0 0 %dbg_main FlagIsLocal
%dbg_i = OpExtInst %void %ext DebugLocalVariable %i_name %dbg_v4f %src 0 0 %dbg_main FlagIsLocal
%main = OpFunction %void None %8
%22 = OpLabel
%s0 = OpExtInst %void %ext DebugScope %dbg_main
%f = OpVariable %_ptr_Function_float Function
%i = OpVariable %_ptr_Function_int Function
OpStore %f %float_0
OpStore %i %int_0
%decl0 = OpExtInst %void %ext DebugValue %dbg_f %f %deref_expr %int_2 %int_1
%decl1 = OpExtInst %void %ext DebugDeclare %dbg_i %i %null_expr
OpBranch %23
%23 = OpLabel
%s1 = OpExtInst %void %ext DebugScope %dbg_main
OpLoopMerge %24 %25 None
OpBranch %26
%26 = OpLabel
%s2 = OpExtInst %void %ext DebugScope %dbg_main
%27 = OpLoad %int %i
%28 = OpSLessThan %bool %27 %int_4
OpBranchConditional %28 %29 %24
%29 = OpLabel
%s3 = OpExtInst %void %ext DebugScope %dbg_main
%30 = OpLoad %float %f
%31 = OpLoad %int %i
%32 = OpAccessChain %_ptr_Input_float %BC %31
%33 = OpLoad %float %32
%34 = OpFAdd %float %30 %33
OpStore %f %34
OpBranch %25
%25 = OpLabel
%s4 = OpExtInst %void %ext DebugScope %dbg_main
%35 = OpLoad %int %i
%36 = OpIAdd %int %35 %int_1
OpStore %i %36
OpBranch %23
%24 = OpLabel
%s5 = OpExtInst %void %ext DebugScope %dbg_main
%37 = OpLoad %float %f
OpStore %fo %37
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<SSARewritePass>(text, true);
}
TEST_F(LocalSSAElimTest, PartiallyKillDebugDeclare) {
// For a reference variable e.g., int i in the following example,
// we do not propagate DebugValue for a store or phi instruction

View File

@ -2079,6 +2079,124 @@ OpFunctionEnd
SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
}
TEST_F(ScalarReplacementTest, DebugValueWithIndex) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
%ext = OpExtInstImport "OpenCL.DebugInfo.100"
OpMemoryModel Logical GLSL450
%test = OpString "test"
OpName %6 "simple_struct"
%1 = OpTypeVoid
%2 = OpTypeInt 32 0
%uint_32 = OpConstant %2 32
%3 = OpTypeStruct %2 %2 %2 %2
%4 = OpTypePointer Function %3
%5 = OpTypePointer Function %2
%6 = OpTypeFunction %2
%7 = OpConstantNull %3
%8 = OpConstant %2 0
%9 = OpConstant %2 1
%10 = OpConstant %2 2
%11 = OpConstant %2 3
%deref = OpExtInst %1 %ext DebugOperation Deref
%deref_expr = OpExtInst %1 %ext DebugExpression %deref
%null_expr = OpExtInst %1 %ext DebugExpression
%src = OpExtInst %1 %ext DebugSource %test
%cu = OpExtInst %1 %ext DebugCompilationUnit 1 4 %src HLSL
%dbg_tf = OpExtInst %1 %ext DebugTypeBasic %test %uint_32 Float
%main_ty = OpExtInst %1 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %1
%dbg_main = OpExtInst %1 %ext DebugFunction %test %main_ty %src 0 0 %cu %test FlagIsProtected|FlagIsPrivate 0 %12
%dbg_foo = OpExtInst %1 %ext DebugLocalVariable %test %dbg_tf %src 0 0 %dbg_main FlagIsLocal
%12 = OpFunction %2 None %6
%13 = OpLabel
%scope = OpExtInst %1 %ext DebugScope %dbg_main
%14 = OpVariable %4 Function %7
; CHECK: [[deref:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugOperation Deref
; CHECK: [[deref_expr:%\w+]] = OpExtInst %void [[ext]] DebugExpression [[deref]]
; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable
; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function
; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function
; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function
; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %uint_0 %uint_1 %uint_2 %int_0
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %uint_0 %uint_1 %uint_2 %int_1
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %uint_0 %uint_1 %uint_2 %int_2
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %uint_0 %uint_1 %uint_2 %int_3
%value = OpExtInst %1 %ext DebugValue %dbg_foo %14 %deref_expr %8 %9 %10
%15 = OpInBoundsAccessChain %5 %14 %8
%16 = OpLoad %2 %15
%17 = OpAccessChain %5 %14 %10
%18 = OpLoad %2 %17
%19 = OpIAdd %2 %16 %18
OpReturnValue %19
OpFunctionEnd
)";
SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
}
TEST_F(ScalarReplacementTest, DebugDeclareForVariableInOtherBB) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
%ext = OpExtInstImport "OpenCL.DebugInfo.100"
OpMemoryModel Logical GLSL450
%test = OpString "test"
OpName %6 "simple_struct"
%1 = OpTypeVoid
%2 = OpTypeInt 32 0
%uint_32 = OpConstant %2 32
%3 = OpTypeStruct %2 %2 %2 %2
%4 = OpTypePointer Function %3
%5 = OpTypePointer Function %2
%6 = OpTypeFunction %2
%7 = OpConstantNull %3
%8 = OpConstant %2 0
%9 = OpConstant %2 1
%10 = OpConstant %2 2
%11 = OpConstant %2 3
%deref = OpExtInst %1 %ext DebugOperation Deref
%deref_expr = OpExtInst %1 %ext DebugExpression %deref
%null_expr = OpExtInst %1 %ext DebugExpression
%src = OpExtInst %1 %ext DebugSource %test
%cu = OpExtInst %1 %ext DebugCompilationUnit 1 4 %src HLSL
%dbg_tf = OpExtInst %1 %ext DebugTypeBasic %test %uint_32 Float
%main_ty = OpExtInst %1 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %1
%dbg_main = OpExtInst %1 %ext DebugFunction %test %main_ty %src 0 0 %cu %test FlagIsProtected|FlagIsPrivate 0 %12
%dbg_foo = OpExtInst %1 %ext DebugLocalVariable %test %dbg_tf %src 0 0 %dbg_main FlagIsLocal
%12 = OpFunction %2 None %6
%13 = OpLabel
%scope = OpExtInst %1 %ext DebugScope %dbg_main
%14 = OpVariable %4 Function %7
; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugLocalVariable
; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr:%\w+]] %int_3
; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2
; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1
; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function
; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0
OpBranch %20
%20 = OpLabel
%value = OpExtInst %1 %ext DebugDeclare %dbg_foo %14 %null_expr
%15 = OpInBoundsAccessChain %5 %14 %8
%16 = OpLoad %2 %15
%17 = OpAccessChain %5 %14 %10
%18 = OpLoad %2 %17
%19 = OpIAdd %2 %16 %18
OpReturnValue %19
OpFunctionEnd
)";
SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
}
} // namespace
} // namespace opt
} // namespace spvtools