diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp --- a/llvm/lib/Transforms/Utils/Local.cpp +++ b/llvm/lib/Transforms/Utils/Local.cpp @@ -1499,7 +1499,7 @@ /// that has an associated llvm.dbg.declare or llvm.dbg.addr intrinsic. void llvm::ConvertDebugDeclareToDebugValue(DbgVariableIntrinsic *DII, StoreInst *SI, DIBuilder &Builder) { - assert(DII->isAddressOfVariable()); + assert(DII->isAddressOfVariable() || isa(DII)); auto *DIVar = DII->getVariable(); assert(DIVar && "Missing variable"); auto *DIExpr = DII->getExpression(); diff --git a/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp b/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp --- a/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp +++ b/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp @@ -100,6 +100,67 @@ namespace { +/// Helper for updating assignment tracking debug info when promoting allocas. +class AssignmentTrackingInfo { + /// DbgAssignIntrinsics linked to the alloca with at most one per variable + /// fragment. (i.e. not be a comprehensive set if there are multiple + /// dbg.assigns for one variable fragment). + SmallVector DbgAssigns; + +public: + void init(AllocaInst *AI) { + SmallSet Vars; + for (DbgAssignIntrinsic *DAI : at::getAssignmentMarkers(AI)) { + if (Vars.insert(DebugVariable(DAI)).second) + DbgAssigns.push_back(DAI); + } + } + + /// Update assignment tracking debug info given for the to-be-deleted store + /// \p ToDelete that stores to this alloca. + void updateForDeletedStore(StoreInst *ToDelete, DIBuilder &DIB) const { + // There's nothing to do if the alloca doesn't have any variables using + // assignment tracking. + if (DbgAssigns.empty()) { + assert(at::getAssignmentMarkers(ToDelete).empty()); + return; + } + + // Just leave dbg.assign intrinsics in place and remember that we've seen + // one for each variable fragment. + SmallSet VarHasDbgAssignForStore; + for (DbgAssignIntrinsic *DAI : at::getAssignmentMarkers(ToDelete)) + VarHasDbgAssignForStore.insert(DebugVariable(DAI)); + + // It's possible for variables using assignment tracking to have no + // dbg.assign linked to this store. These are variables in DbgAssigns that + // are missing from VarHasDbgAssignForStore. Since there isn't a dbg.assign + // to mark the assignment - and the store is going to be deleted - insert a + // dbg.value to do that now. An untracked store may be either one that + // cannot be represented using assignment tracking (non-const offset or + // size) or one that is trackable but has had its DIAssignID attachment + // dropped accidentally. + for (auto *DAI : DbgAssigns) { + if (VarHasDbgAssignForStore.contains(DebugVariable(DAI))) + continue; + ConvertDebugDeclareToDebugValue(DAI, ToDelete, DIB); + } + } + + /// Update assignment tracking debug info given for the newly inserted PHI \p + /// NewPhi. + void updateForNewPhi(PHINode *NewPhi, DIBuilder &DIB) const { + // Regardless of the position of dbg.assigns relative to stores, the + // incoming values into a new PHI should be the same for the (imaginary) + // debug-phi. + for (auto *DAI : DbgAssigns) + ConvertDebugDeclareToDebugValue(DAI, NewPhi, DIB); + } + + void clear() { DbgAssigns.clear(); } + bool empty() { return DbgAssigns.empty(); } +}; + struct AllocaInfo { using DbgUserVec = SmallVector; @@ -110,7 +171,10 @@ BasicBlock *OnlyBlock; bool OnlyUsedInOneBlock; + /// Debug users of the alloca - does not include dbg.assign intrinsics. DbgUserVec DbgUsers; + /// Helper to update assignment tracking debug info. + AssignmentTrackingInfo AssignmentTracking; void clear() { DefiningBlocks.clear(); @@ -119,6 +183,7 @@ OnlyBlock = nullptr; OnlyUsedInOneBlock = true; DbgUsers.clear(); + AssignmentTracking.clear(); } /// Scan the uses of the specified alloca, filling in the AllocaInfo used @@ -150,8 +215,13 @@ OnlyUsedInOneBlock = false; } } - - findDbgUsers(DbgUsers, AI); + DbgUserVec AllDbgUsers; + findDbgUsers(AllDbgUsers, AI); + std::copy_if(AllDbgUsers.begin(), AllDbgUsers.end(), + std::back_inserter(DbgUsers), [](DbgVariableIntrinsic *DII) { + return !isa(DII); + }); + AssignmentTracking.init(AI); } }; @@ -251,6 +321,10 @@ /// intrinsic if the alloca gets promoted. SmallVector AllocaDbgUsers; + /// For each alloca, keep an instance of a helper class that gives us an easy + /// way to update assignment tracking debug info if the alloca is promoted. + SmallVector AllocaATInfo; + /// The set of basic blocks the renamer has already visited. SmallPtrSet Visited; @@ -417,17 +491,24 @@ if (!Info.UsingBlocks.empty()) return false; // If not, we'll have to fall back for the remainder. + DIBuilder DIB(*AI->getModule(), /*AllowUnresolved*/ false); + // Update assignment tracking info for the store we're going to delete. + Info.AssignmentTracking.updateForDeletedStore(Info.OnlyStore, DIB); + // Record debuginfo for the store and remove the declaration's // debuginfo. for (DbgVariableIntrinsic *DII : Info.DbgUsers) { if (DII->isAddressOfVariable()) { - DIBuilder DIB(*AI->getModule(), /*AllowUnresolved*/ false); ConvertDebugDeclareToDebugValue(DII, Info.OnlyStore, DIB); DII->eraseFromParent(); } else if (DII->getExpression()->startsWithDeref()) { DII->eraseFromParent(); } } + + // Remove dbg.assigns linked to the alloca as these are now redundant. + at::deleteAssignmentMarkers(AI); + // Remove the (now dead) store and alloca. Info.OnlyStore->eraseFromParent(); LBI.deleteValue(Info.OnlyStore); @@ -520,12 +601,14 @@ } // Remove the (now dead) stores and alloca. + DIBuilder DIB(*AI->getModule(), /*AllowUnresolved*/ false); while (!AI->use_empty()) { StoreInst *SI = cast(AI->user_back()); + // Update assignment tracking info for the store we're going to delete. + Info.AssignmentTracking.updateForDeletedStore(SI, DIB); // Record debuginfo for the store before removing it. for (DbgVariableIntrinsic *DII : Info.DbgUsers) { if (DII->isAddressOfVariable()) { - DIBuilder DIB(*AI->getModule(), /*AllowUnresolved*/ false); ConvertDebugDeclareToDebugValue(DII, SI, DIB); } } @@ -533,6 +616,8 @@ LBI.deleteValue(SI); } + // Remove dbg.assigns linked to the alloca as these are now redundant. + at::deleteAssignmentMarkers(AI); AI->eraseFromParent(); // The alloca's debuginfo can be removed as well. @@ -548,6 +633,7 @@ Function &F = *DT.getRoot()->getParent(); AllocaDbgUsers.resize(Allocas.size()); + AllocaATInfo.resize(Allocas.size()); AllocaInfo Info; LargeBlockInfo LBI; @@ -607,6 +693,8 @@ // Remember the dbg.declare intrinsic describing this alloca, if any. if (!Info.DbgUsers.empty()) AllocaDbgUsers[AllocaNum] = Info.DbgUsers; + if (!Info.AssignmentTracking.empty()) + AllocaATInfo[AllocaNum] = Info.AssignmentTracking; // Keep the reverse mapping of the 'Allocas' array for the rename pass. AllocaLookup[Allocas[AllocaNum]] = AllocaNum; @@ -670,6 +758,8 @@ // Remove the allocas themselves from the function. for (Instruction *A : Allocas) { + // Remove dbg.assigns linked to the alloca as these are now redundant. + at::deleteAssignmentMarkers(A); // If there are any uses of the alloca instructions left, they must be in // unreachable basic blocks that were not processed by walking the dominator // tree. Just delete the users now. @@ -923,6 +1013,7 @@ // The currently active variable for this block is now the PHI. IncomingVals[AllocaNo] = APN; + AllocaATInfo[AllocaNo].updateForNewPhi(APN, DIB); for (DbgVariableIntrinsic *DII : AllocaDbgUsers[AllocaNo]) if (DII->isAddressOfVariable()) ConvertDebugDeclareToDebugValue(DII, APN, DIB); @@ -984,6 +1075,7 @@ // Record debuginfo for the store before removing it. IncomingLocs[AllocaNo] = SI->getDebugLoc(); + AllocaATInfo[AllocaNo].updateForDeletedStore(SI, DIB); for (DbgVariableIntrinsic *DII : AllocaDbgUsers[ai->second]) if (DII->isAddressOfVariable()) ConvertDebugDeclareToDebugValue(DII, SI, DIB); diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/phi.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/phi.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/phi.ll @@ -0,0 +1,96 @@ +; RUN: opt -passes=mem2reg -S %s -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg" + +;; Test assignment tracking debug info when mem2reg promotes an alloca with +;; stores requiring insertion of a phi. Check the output when the stores are +;; tagged and also untagged (test manually updated for the latter by linking a +;; dbg.assgin for another variable "b" to the alloca). + +; CHECK: entry: +; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a, metadata ![[B:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %a, metadata ![[A:[0-9]+]], {{.*}}, metadata i32* undef +; CHECK: if.then: +; CHECK-NEXT: %add = +; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %add, metadata ![[B]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %add, metadata ![[A]], {{.*}}, metadata i32* undef +; CHECK: if.else: +; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 -1, metadata ![[B]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 -1, metadata ![[A]], {{.*}}, metadata i32* undef +; CHECK: if.end: +; CHECK-NEXT: %a.addr.0 = phi i32 +; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a.addr.0, metadata ![[A]] +; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a.addr.0, metadata ![[B]] + +; CHECK-DAG: ![[A]] = !DILocalVariable(name: "a", +; CHECK-DAG: ![[B]] = !DILocalVariable(name: "b", + +;; $ cat test.cpp +;; int f(int a) { +;; if (a) +;; a += 1; +;; else +;; a = -1; +;; return a; +;; } + +define dso_local noundef i32 @_Z1fi(i32 noundef %a) #0 !dbg !7 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + call void @llvm.dbg.assign(metadata i1 undef, metadata !30, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + store i32 %a, i32* %a.addr, align 4, !DIAssignID !19 + call void @llvm.dbg.assign(metadata i32 %a, metadata !12, metadata !DIExpression(), metadata !19, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + %0 = load i32, i32* %a.addr, align 4, !dbg !20 + %tobool = icmp ne i32 %0, 0, !dbg !20 + br i1 %tobool, label %if.then, label %if.else, !dbg !22 + +if.then: ; preds = %entry + %1 = load i32, i32* %a.addr, align 4, !dbg !23 + %add = add nsw i32 %1, 1, !dbg !23 + store i32 %add, i32* %a.addr, align 4, !dbg !23, !DIAssignID !24 + call void @llvm.dbg.assign(metadata i32 %add, metadata !12, metadata !DIExpression(), metadata !24, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + br label %if.end, !dbg !25 + +if.else: ; preds = %entry + store i32 -1, i32* %a.addr, align 4, !dbg !26, !DIAssignID !27 + call void @llvm.dbg.assign(metadata i32 -1, metadata !12, metadata !DIExpression(), metadata !27, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + br label %if.end + +if.end: ; preds = %if.else, %if.then + %2 = load i32, i32* %a.addr, align 4, !dbg !28 + ret i32 %2, !dbg !29 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0"} +!7 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!12} +!12 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 1, type: !10) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 2, column: 7, scope: !21) +!21 = distinct !DILexicalBlock(scope: !7, file: !1, line: 2, column: 7) +!22 = !DILocation(line: 2, column: 7, scope: !7) +!23 = !DILocation(line: 3, column: 7, scope: !21) +!24 = distinct !DIAssignID() +!25 = !DILocation(line: 3, column: 5, scope: !21) +!26 = !DILocation(line: 5, column: 7, scope: !21) +!27 = distinct !DIAssignID() +!28 = !DILocation(line: 6, column: 10, scope: !7) +!29 = !DILocation(line: 6, column: 3, scope: !7) +!30 = !DILocalVariable(name: "b", arg: 2, scope: !7, file: !1, line: 1, type: !10) diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-block-alloca.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-block-alloca.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-block-alloca.ll @@ -0,0 +1,66 @@ +; RUN: opt -passes=mem2reg -S %s -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg" + +;; Test assignment tracking debug info when mem2reg promotes a single-block +;; alloca. Check the output when the stores are tagged and also untagged (test +;; manually updated for the latter by linking a dbg.assgin for another variable +;; "b" to the alloca). + +; CHECK: entry: +; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a, metadata ![[B:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %a, metadata ![[A:[0-9]+]], {{.*}}, metadata i32* undef +; CHECK-NEXT: %add = +; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %add, metadata ![[B]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %add, metadata ![[A]], {{.*}}, metadata i32* undef + +; CHECK-DAG: ![[A]] = !DILocalVariable(name: "a", +; CHECK-DAG: ![[B]] = !DILocalVariable(name: "b", + +;; $ cat test.cpp +;; int f(int a) { +;; a += 1; +;; return a; +;; } + +define dso_local noundef i32 @_Z1fi(i32 noundef %a) !dbg !7 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + call void @llvm.dbg.assign(metadata i1 undef, metadata !24, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + store i32 %a, i32* %a.addr, align 4, !DIAssignID !19 + call void @llvm.dbg.assign(metadata i32 %a, metadata !12, metadata !DIExpression(), metadata !19, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + %0 = load i32, i32* %a.addr, align 4, !dbg !20 + %add = add nsw i32 %0, 1, !dbg !20 + store i32 %add, i32* %a.addr, align 4, !dbg !20, !DIAssignID !21 + call void @llvm.dbg.assign(metadata i32 %add, metadata !12, metadata !DIExpression(), metadata !21, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + %1 = load i32, i32* %a.addr, align 4, !dbg !22 + ret i32 %1, !dbg !23 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0"} +!7 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!12} +!12 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 1, type: !10) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 2, column: 5, scope: !7) +!21 = distinct !DIAssignID() +!22 = !DILocation(line: 3, column: 10, scope: !7) +!23 = !DILocation(line: 3, column: 3, scope: !7) +!24 = !DILocalVariable(name: "b", scope: !7, file: !1, line: 1, type: !10) diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-store-alloca.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-store-alloca.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-store-alloca.ll @@ -0,0 +1,62 @@ +; RUN: opt -passes=mem2reg -S %s -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg" + +;; Test assignment tracking debug info when mem2reg promotes a single-store +;; alloca. Additionally, check that all the dbg.assigns linked to the alloca +;; are cleaned up, including duplciates. + +; CHECK: entry: +; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a, metadata ![[B:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %a, metadata ![[A:[0-9]+]], {{.*}}, metadata i32* undef +; CHECK-NEXT: ret + +; CHECK-DAG: ![[A]] = !DILocalVariable(name: "a", +; CHECK-DAG: ![[B]] = !DILocalVariable(name: "b", + +;; 1. using source: +;; $ cat test.cpp +;; int f(int a) { return a; } +;; 2. manually duplicating the dbg.assign lnked to a's alloca to ensure +;; duplicates are still cleaned up, +;; 3. and with a dbg.assign for another variable ("b") attached to a's alloca +;; to check that a dbg.value is generated for the variable to represent the +;; store despite the store not having a dbg.assign linked for it. + +; Function Attrs: mustprogress nounwind uwtable +define dso_local noundef i32 @_Z1fi(i32 noundef %a) #0 !dbg !7 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + call void @llvm.dbg.assign(metadata i1 undef, metadata !22, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + store i32 %a, i32* %a.addr, align 4, !DIAssignID !19 + call void @llvm.dbg.assign(metadata i32 %a, metadata !12, metadata !DIExpression(), metadata !19, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14 + %0 = load i32, i32* %a.addr, align 4, !dbg !20 + ret i32 %0, !dbg !21 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0)"} +!7 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!12} +!12 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 1, type: !10) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 1, column: 23, scope: !7) +!21 = !DILocation(line: 1, column: 16, scope: !7) +!22 = !DILocalVariable(name: "b", scope: !7, file: !1, line: 1, type: !10)