[Assignment Tracking][4/*] Add llvm.dbg.assign intrinsic boilerplate
authorOCHyams <orlando.hyams@sony.com>
Mon, 7 Nov 2022 09:31:45 +0000 (09:31 +0000)
committerOCHyams <orlando.hyams@sony.com>
Mon, 7 Nov 2022 10:09:22 +0000 (10:09 +0000)
The Assignment Tracking debug-info feature is outlined in this RFC:

https://discourse.llvm.org/t/
rfc-assignment-tracking-a-better-way-of-specifying-variable-locations-in-ir

Add the llvm.dbg.assign intrinsic boilerplate. This updates the textual-bitcode
roundtrip test to also check that round-tripping with the intrinsic works.

The intrinsic marks the position of a source level assignment.

The llvm.dbg.assign interface looks like this (each parameter is wrapped in
MetadataAsValue, and Value * type parameters are first wrapped in
ValueAsMetadata):

    void @llvm.dbg.assign(Value *Value,
                          DIExpression *ValueExpression,
                          DILocalVariable *Variable,
                          DIAssignID *ID,
                          Value *Address,
                          DIExpression *AddressExpression)

The first three parameters look and behave like an llvm.dbg.value. ID is a
reference to a store. The intrinsic is "linked to" instructions in the same
function that use the same ID as an attachment. That is mostly conceptual at
this point; the two-way link infrastructure will come in another patch. Address
is the destination address of the store and it is modified by
AddressExpression. LLVM currently encodes variable fragment information in
DIExpressions, so as an implementation quirk the FragmentInfo for Variable is
contained within ValueExpression only.

Reviewed By: jmorse

Differential Revision: https://reviews.llvm.org/D132223

llvm/include/llvm/IR/IntrinsicInst.h
llvm/include/llvm/IR/Intrinsics.td
llvm/lib/IR/IntrinsicInst.cpp
llvm/lib/IR/Verifier.cpp
llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/roundtrip.ll
llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/verify.ll [new file with mode: 0644]

index 4ff48c3..f78e45c 100644 (file)
@@ -91,6 +91,7 @@ public:
     case Intrinsic::assume:
     case Intrinsic::sideeffect:
     case Intrinsic::pseudoprobe:
+    case Intrinsic::dbg_assign:
     case Intrinsic::dbg_declare:
     case Intrinsic::dbg_value:
     case Intrinsic::dbg_label:
@@ -129,6 +130,7 @@ static inline bool isDbgInfoIntrinsic(Intrinsic::ID ID) {
   case Intrinsic::dbg_value:
   case Intrinsic::dbg_addr:
   case Intrinsic::dbg_label:
+  case Intrinsic::dbg_assign:
     return true;
   default:
     return false;
@@ -231,10 +233,12 @@ public:
 
   bool hasArgList() const { return isa<DIArgList>(getRawLocation()); }
 
-  /// Does this describe the address of a local variable. True for dbg.addr
-  /// and dbg.declare, but not dbg.value, which describes its value.
+  /// Does this describe the address of a local variable. True for dbg.addr and
+  /// dbg.declare, but not dbg.value, which describes its value, or dbg.assign,
+  /// which describes a combination of the variable's value and address.
   bool isAddressOfVariable() const {
-    return getIntrinsicID() != Intrinsic::dbg_value;
+    return getIntrinsicID() != Intrinsic::dbg_value &&
+           getIntrinsicID() != Intrinsic::dbg_assign;
   }
 
   void setUndef() {
@@ -286,6 +290,11 @@ public:
   /// is described.
   Optional<uint64_t> getFragmentSizeInBits() const;
 
+  /// Get the FragmentInfo for the variable.
+  Optional<DIExpression::FragmentInfo> getFragment() const {
+    return getExpression()->getFragmentInfo();
+  }
+
   /// \name Casting methods
   /// @{
   static bool classof(const IntrinsicInst *I) {
@@ -293,6 +302,7 @@ public:
     case Intrinsic::dbg_declare:
     case Intrinsic::dbg_value:
     case Intrinsic::dbg_addr:
+    case Intrinsic::dbg_assign:
       return true;
     default:
       return false;
@@ -302,7 +312,7 @@ public:
     return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
   }
   /// @}
-private:
+protected:
   void setArgOperand(unsigned i, Value *v) {
     DbgInfoIntrinsic::setArgOperand(i, v);
   }
@@ -363,7 +373,52 @@ public:
   /// \name Casting methods
   /// @{
   static bool classof(const IntrinsicInst *I) {
-    return I->getIntrinsicID() == Intrinsic::dbg_value;
+    return I->getIntrinsicID() == Intrinsic::dbg_value ||
+           I->getIntrinsicID() == Intrinsic::dbg_assign;
+  }
+  static bool classof(const Value *V) {
+    return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
+  }
+  /// @}
+};
+
+/// This represents the llvm.dbg.assign instruction.
+class DbgAssignIntrinsic : public DbgValueInst {
+  enum Operands {
+    OpValue,
+    OpVar,
+    OpExpr,
+    OpAssignID,
+    OpAddress,
+    OpAddressExpr,
+  };
+
+public:
+  Value *getAddress() const;
+  Metadata *getRawAddress() const {
+    return cast<MetadataAsValue>(getArgOperand(OpAddress))->getMetadata();
+  }
+  Metadata *getRawAssignID() const {
+    return cast<MetadataAsValue>(getArgOperand(OpAssignID))->getMetadata();
+  }
+  DIAssignID *getAssignID() const { return cast<DIAssignID>(getRawAssignID()); }
+  Metadata *getRawAddressExpression() const {
+    return cast<MetadataAsValue>(getArgOperand(OpAddressExpr))->getMetadata();
+  }
+  DIExpression *getAddressExpression() const {
+    return cast<DIExpression>(getRawAddressExpression());
+  }
+  void setAddressExpression(DIExpression *NewExpr) {
+    setArgOperand(OpAddressExpr,
+                  MetadataAsValue::get(NewExpr->getContext(), NewExpr));
+  }
+  void setAssignId(DIAssignID *New);
+  void setAddress(Value *V);
+  void setValue(Value *V);
+  /// \name Casting methods
+  /// @{
+  static bool classof(const IntrinsicInst *I) {
+    return I->getIntrinsicID() == Intrinsic::dbg_assign;
   }
   static bool classof(const Value *V) {
     return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
index e8fb5c4..3050bd2 100644 (file)
@@ -998,6 +998,13 @@ let IntrProperties = [IntrNoMem, IntrSpeculatable, IntrWillReturn] in {
                                        [llvm_metadata_ty,
                                         llvm_metadata_ty,
                                         llvm_metadata_ty]>;
+  def int_dbg_assign        : DefaultAttrsIntrinsic<[],
+                                       [llvm_metadata_ty,
+                                        llvm_metadata_ty,
+                                        llvm_metadata_ty,
+                                        llvm_metadata_ty,
+                                        llvm_metadata_ty,
+                                        llvm_metadata_ty]>;
   def int_dbg_label        : DefaultAttrsIntrinsic<[],
                                        [llvm_metadata_ty]>;
 }
index 8ca75f5..b6537b2 100644 (file)
@@ -112,10 +112,23 @@ static ValueAsMetadata *getAsMetadata(Value *V) {
 
 void DbgVariableIntrinsic::replaceVariableLocationOp(Value *OldValue,
                                                      Value *NewValue) {
+  // If OldValue is used as the address part of a dbg.assign intrinsic replace
+  // it with NewValue and return true.
+  auto ReplaceDbgAssignAddress = [this, OldValue, NewValue]() -> bool {
+    auto *DAI = dyn_cast<DbgAssignIntrinsic>(this);
+    if (!DAI || OldValue != DAI->getAddress())
+      return false;
+    DAI->setAddress(NewValue);
+    return true;
+  };
+  bool DbgAssignAddrReplaced = ReplaceDbgAssignAddress();
+  (void)DbgAssignAddrReplaced;
+
   assert(NewValue && "Values must be non-null");
   auto Locations = location_ops();
   auto OldIt = find(Locations, OldValue);
-  assert(OldIt != Locations.end() && "OldValue must be a current location");
+  assert((OldIt != Locations.end() || DbgAssignAddrReplaced) &&
+         "OldValue must be a current location");
   if (!hasArgList()) {
     Value *NewOperand = isa<MetadataAsValue>(NewValue)
                             ? NewValue
@@ -172,6 +185,32 @@ Optional<uint64_t> DbgVariableIntrinsic::getFragmentSizeInBits() const {
   return getVariable()->getSizeInBits();
 }
 
+Value *DbgAssignIntrinsic::getAddress() const {
+  auto *MD = getRawAddress();
+  if (auto *V = dyn_cast<ValueAsMetadata>(MD))
+    return V->getValue();
+
+  // When the value goes to null, it gets replaced by an empty MDNode.
+  assert(!cast<MDNode>(MD)->getNumOperands() && "Expected an empty MDNode");
+  return nullptr;
+}
+
+void DbgAssignIntrinsic::setAssignId(DIAssignID *New) {
+  setOperand(OpAssignID, MetadataAsValue::get(getContext(), New));
+}
+
+void DbgAssignIntrinsic::setAddress(Value *V) {
+  assert(V->getType()->isPointerTy() &&
+         "Destination Component must be a pointer type");
+  setOperand(OpAddress,
+             MetadataAsValue::get(getContext(), ValueAsMetadata::get(V)));
+}
+
+void DbgAssignIntrinsic::setValue(Value *V) {
+  setOperand(OpValue,
+             MetadataAsValue::get(getContext(), ValueAsMetadata::get(V)));
+}
+
 int llvm::Intrinsic::lookupLLVMIntrinsicByName(ArrayRef<const char *> NameTable,
                                                StringRef Name) {
   assert(Name.startswith("llvm."));
index f0097da..324fa66 100644 (file)
@@ -4541,6 +4541,15 @@ void Verifier::visitDIAssignIDMetadata(Instruction &I, MDNode *MD) {
       isa<AllocaInst>(I) || isa<StoreInst>(I) || isa<MemIntrinsic>(I);
   CheckDI(ExpectedInstTy, "!DIAssignID attached to unexpected instruction kind",
           I, MD);
+  // Iterate over the MetadataAsValue uses of the DIAssignID - these should
+  // only be found as DbgAssignIntrinsic operands.
+  if (auto *AsValue = MetadataAsValue::getIfExists(Context, MD)) {
+    for (auto *User : AsValue->users()) {
+      CheckDI(isa<DbgAssignIntrinsic>(User),
+              "!DIAssignID should only be used by llvm.dbg.assign intrinsics",
+              MD, User);
+    }
+  }
 }
 
 void Verifier::visitCallStackMetadata(MDNode *MD) {
@@ -5023,6 +5032,9 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
   case Intrinsic::dbg_value: // llvm.dbg.value
     visitDbgIntrinsic("value", cast<DbgVariableIntrinsic>(Call));
     break;
+  case Intrinsic::dbg_assign: // llvm.dbg.assign
+    visitDbgIntrinsic("assign", cast<DbgVariableIntrinsic>(Call));
+    break;
   case Intrinsic::dbg_label: // llvm.dbg.label
     visitDbgLabelIntrinsic("label", cast<DbgLabelInst>(Call));
     break;
@@ -5986,6 +5998,18 @@ void Verifier::visitDbgIntrinsic(StringRef Kind, DbgVariableIntrinsic &DII) {
           "invalid llvm.dbg." + Kind + " intrinsic expression", &DII,
           DII.getRawExpression());
 
+  if (auto *DAI = dyn_cast<DbgAssignIntrinsic>(&DII)) {
+    CheckDI(isa<DIAssignID>(DAI->getRawAssignID()),
+            "invalid llvm.dbg.assign intrinsic DIAssignID", &DII,
+            DAI->getRawAssignID());
+    CheckDI(isa<ValueAsMetadata>(DAI->getRawAddress()),
+            "invalid llvm.dbg.assign intrinsic address)", &DII,
+            DAI->getRawAddress());
+    CheckDI(isa<DIExpression>(DAI->getRawAddressExpression()),
+            "invalid llvm.dbg.assign intrinsic address expression", &DII,
+            DAI->getRawAddressExpression());
+  }
+
   // Ignore broken !dbg attachments; they're checked elsewhere.
   if (MDNode *N = DII.getDebugLoc().getAsMDNode())
     if (!isa<DILocation>(N))
index 1ddb95b..808636a 100644 (file)
@@ -2,17 +2,81 @@
 ; RUN: | opt -verify -S -experimental-assignment-tracking \
 ; RUN: | FileCheck %s
 
-;; Roundtrip test (text -> bitcode -> text) for DIAssignID attachments.
-
-; CHECK: %local = alloca i32, align 4, !DIAssignID ![[ID:[0-9]+]]
-; CHECK-DAG: ![[ID]] = distinct !DIAssignID()
+;; Roundtrip test (text -> bitcode -> text) for DIAssignID metadata and
+;; llvm.dbg.assign intrinsics.
 
+;; DIAssignID attachment only.
+; CHECK-LABEL: @fun()
+; CHECK: %local = alloca i32, align 4, !DIAssignID ![[ID1:[0-9]+]]
 define dso_local void @fun() !dbg !7 {
 entry:
   %local = alloca i32, align 4, !DIAssignID !14
   ret void, !dbg !13
 }
 
+;; Unlinked llvm.dbg.assign.
+; CHECK-DAG: @fun2()
+; CHECK: llvm.dbg.assign(metadata i32 undef, metadata ![[VAR2:[0-9]+]], metadata !DIExpression(), metadata ![[ID2:[0-9]+]], metadata i32 undef, metadata !DIExpression()), !dbg ![[DBG2:[0-9]+]]
+define dso_local void @fun2() !dbg !15 {
+entry:
+  %local = alloca i32, align 4
+  call void @llvm.dbg.assign(metadata i32 undef, metadata !16, metadata !DIExpression(), metadata !18, metadata i32 undef, metadata !DIExpression()), !dbg !17
+  ret void, !dbg !17
+}
+
+;; An llvm.dbg.assign linked to an alloca.
+; CHECK-LABEL: @fun3()
+; CHECK: %local = alloca i32, align 4, !DIAssignID ![[ID3:[0-9]+]]
+; CHECK-NEXT: llvm.dbg.assign(metadata i32 undef, metadata ![[VAR3:[0-9]+]], metadata !DIExpression(), metadata ![[ID3]], metadata i32 undef, metadata !DIExpression()), !dbg ![[DBG3:[0-9]+]]
+define dso_local void @fun3() !dbg !19 {
+entry:
+  %local = alloca i32, align 4, !DIAssignID !22
+  call void @llvm.dbg.assign(metadata i32 undef, metadata !20, metadata !DIExpression(), metadata !22, metadata i32 undef, metadata !DIExpression()), !dbg !21
+  ret void, !dbg !21
+}
+
+;; Check that using a DIAssignID as an operand before using it as an attachment
+;; works (the order of the alloca and dbg.assign has been swapped).
+; CHECK-LABEL: @fun4()
+; CHECK: llvm.dbg.assign(metadata i32 undef, metadata ![[VAR4:[0-9]+]], metadata !DIExpression(), metadata ![[ID4:[0-9]+]], metadata i32 undef, metadata !DIExpression()), !dbg ![[DBG4:[0-9]+]]
+; CHECK-NEXT: %local = alloca i32, align 4, !DIAssignID ![[ID4]]
+define dso_local void @fun4() !dbg !23 {
+entry:
+  call void @llvm.dbg.assign(metadata i32 undef, metadata !24, metadata !DIExpression(), metadata !26, metadata i32 undef, metadata !DIExpression()), !dbg !25
+  %local = alloca i32, align 4, !DIAssignID !26
+  ret void, !dbg !25
+}
+
+;; Check that the value and address operands print correctly.
+;; There are currently no plans to support DIArgLists for the address component.
+; CHECK-LABEL: @fun5
+; CHECK: %local = alloca i32, align 4, !DIAssignID ![[ID5:[0-9]+]]
+; CHECK-NEXT: llvm.dbg.assign(metadata i32 %v, metadata ![[VAR5:[0-9]+]], metadata !DIExpression(), metadata ![[ID5]], metadata i32* %local, metadata !DIExpression()), !dbg ![[DBG5:[0-9]+]]
+; CHECK-NEXT: llvm.dbg.assign(metadata !DIArgList(i32 %v, i32 1), metadata ![[VAR5]], metadata !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_minus, DW_OP_stack_value), metadata ![[ID5]], metadata i32* %local, metadata !DIExpression()), !dbg ![[DBG5]]
+define dso_local void @fun5(i32 %v) !dbg !27 {
+entry:
+  %local = alloca i32, align 4, !DIAssignID !30
+  call void @llvm.dbg.assign(metadata i32 %v, metadata !28, metadata !DIExpression(), metadata !30, metadata i32* %local, metadata !DIExpression()), !dbg !29
+  call void @llvm.dbg.assign(metadata !DIArgList(i32 %v, i32 1), metadata !28, metadata !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_minus, DW_OP_stack_value), metadata !30, metadata i32* %local, metadata !DIExpression()), !dbg !29
+  ret void
+}
+
+; CHECK-DAG: ![[ID1]] = distinct !DIAssignID()
+; CHECK-DAG: ![[ID2]] = distinct !DIAssignID()
+; CHECK-DAG: ![[VAR2]] = !DILocalVariable(name: "local2",
+; CHECK-DAG: ![[DBG2]] = !DILocation(line: 2
+; CHECK-DAG: ![[ID3]] = distinct !DIAssignID()
+; CHECK-DAG: ![[VAR3]] = !DILocalVariable(name: "local3",
+; CHECK-DAG: ![[DBG3]] = !DILocation(line: 3,
+; CHECK-DAG: ![[ID4]] = distinct !DIAssignID()
+; CHECK-DAG: ![[VAR4]] = !DILocalVariable(name: "local4",
+; CHECK-DAG: ![[DBG4]] = !DILocation(line: 4,
+; CHECK-DAG: ![[ID5]] = distinct !DIAssignID()
+; CHECK-DAG: ![[VAR5]] = !DILocalVariable(name: "local5",
+; CHECK-DAG: ![[DBG5]] = !DILocation(line: 5,
+
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
+
 !llvm.dbg.cu = !{!0}
 !llvm.module.flags = !{!3, !4, !5}
 !llvm.ident = !{!6}
@@ -29,5 +93,23 @@ entry:
 !9 = !{null}
 !10 = !DILocalVariable(name: "local", scope: !7, file: !1, line: 2, type: !11)
 !11 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
-!13 = !DILocation(line: 3, column: 1, scope: !7)
+!13 = !DILocation(line: 1, column: 1, scope: !7)
 !14 = distinct !DIAssignID()
+!15 = distinct !DISubprogram(name: "fun2", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!16 = !DILocalVariable(name: "local2", scope: !15, file: !1, line: 2, type: !11)
+!17 = !DILocation(line: 2, column: 1, scope: !15)
+!18 = distinct !DIAssignID()
+!19 = distinct !DISubprogram(name: "fun3", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!20 = !DILocalVariable(name: "local3", scope: !19, file: !1, line: 2, type: !11)
+!21 = !DILocation(line: 3, column: 1, scope: !19)
+!22 = distinct !DIAssignID()
+!23 = distinct !DISubprogram(name: "fun4", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!24 = !DILocalVariable(name: "local4", scope: !23, file: !1, line: 2, type: !11)
+!25 = !DILocation(line: 4, column: 1, scope: !23)
+!26 = distinct !DIAssignID()
+!27 = distinct !DISubprogram(name: "fun5", scope: !1, file: !1, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!28 = !DILocalVariable(name: "local5", scope: !27, file: !1, line: 2, type: !11)
+!29 = !DILocation(line: 5, column: 1, scope: !27)
+!30 = distinct !DIAssignID()
+!31 = !DISubroutineType(types: !32)
+!32 = !{null, !11}
diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/verify.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/verify.ll
new file mode 100644 (file)
index 0000000..5772896
--- /dev/null
@@ -0,0 +1,52 @@
+; RUN: opt %s -S -verify -experimental-assignment-tracking 2>&1 \
+; RUN: | FileCheck %s
+
+;; Check that badly formed assignment tracking metadata is caught either
+;; while parsing or by the verifier.
+;;
+;; Checks for this one are inline.
+
+define dso_local void @fun() !dbg !7 {
+entry:
+  %a = alloca i32, align 4, !DIAssignID !14
+  ;; Here something other than a dbg.assign intrinsic is using a DIAssignID.
+  ; CHECK: !DIAssignID should only be used by llvm.dbg.assign intrinsics
+  call void @llvm.dbg.value(metadata !14, metadata !10, metadata !DIExpression()), !dbg !13
+
+  ;; Each following dbg.assign has an argument of the incorrect type.
+  ; CHECK: invalid llvm.dbg.assign intrinsic address/value
+  call void @llvm.dbg.assign(metadata !3, metadata !10, metadata !DIExpression(), metadata !14, metadata i32* undef, metadata !DIExpression()), !dbg !13
+  ; CHECK: invalid llvm.dbg.assign intrinsic variable
+  call void @llvm.dbg.assign(metadata i32 0, metadata !2, metadata !DIExpression(), metadata !14, metadata i32* undef, metadata !DIExpression()), !dbg !13
+  ; CHECK: invalid llvm.dbg.assign intrinsic expression
+  call void @llvm.dbg.assign(metadata !14, metadata !10, metadata !2, metadata !14, metadata i32* undef, metadata !DIExpression()), !dbg !13
+  ; CHECK: invalid llvm.dbg.assign intrinsic DIAssignID
+  call void @llvm.dbg.assign(metadata !14, metadata !10, metadata !DIExpression(), metadata !2, metadata i32* undef, metadata !DIExpression()), !dbg !13
+  ; CHECK: invalid llvm.dbg.assign intrinsic address
+  call void @llvm.dbg.assign(metadata !14, metadata !10, metadata !DIExpression(), metadata !14, metadata !3, metadata !DIExpression()), !dbg !13
+  ; CHECK: invalid llvm.dbg.assign intrinsic address expression
+  call void @llvm.dbg.assign(metadata !14, metadata !10, metadata !DIExpression(), metadata !14, metadata i32* undef, metadata !2), !dbg !13
+  ret void
+}
+
+declare void @llvm.dbg.value(metadata, metadata, metadata)
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 14.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "/")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !{!"clang version 14.0.0"}
+!7 = distinct !DISubprogram(name: "fun", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!8 = !DISubroutineType(types: !9)
+!9 = !{null}
+!10 = !DILocalVariable(name: "local", scope: !7, file: !1, line: 2, type: !11)
+!11 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!13 = !DILocation(line: 1, column: 1, scope: !7)
+!14 = distinct !DIAssignID()