diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -91,6 +91,7 @@ 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 @@ 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 @@ bool hasArgList() const { return isa(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 @@ /// is described. Optional getFragmentSizeInBits() const; + /// Get the FragmentInfo for the variable. + Optional getFragment() const { + return getExpression()->getFragmentInfo(); + } + /// \name Casting methods /// @{ static bool classof(const IntrinsicInst *I) { @@ -293,6 +302,7 @@ 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 @@ return isa(V) && classof(cast(V)); } /// @} -private: +protected: void setArgOperand(unsigned i, Value *v) { DbgInfoIntrinsic::setArgOperand(i, v); } @@ -363,7 +373,52 @@ /// \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(V) && classof(cast(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(getArgOperand(OpAddress))->getMetadata(); + } + Metadata *getRawAssignID() const { + return cast(getArgOperand(OpAssignID))->getMetadata(); + } + DIAssignID *getAssignID() const { return cast(getRawAssignID()); } + Metadata *getRawAddressExpression() const { + return cast(getArgOperand(OpAddressExpr))->getMetadata(); + } + DIExpression *getAddressExpression() const { + return cast(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(V) && classof(cast(V)); diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -998,6 +998,13 @@ [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]>; } diff --git a/llvm/lib/IR/IntrinsicInst.cpp b/llvm/lib/IR/IntrinsicInst.cpp --- a/llvm/lib/IR/IntrinsicInst.cpp +++ b/llvm/lib/IR/IntrinsicInst.cpp @@ -112,10 +112,23 @@ 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(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(NewValue) ? NewValue @@ -172,6 +185,32 @@ return getVariable()->getSizeInBits(); } +Value *DbgAssignIntrinsic::getAddress() const { + auto *MD = getRawAddress(); + if (auto *V = dyn_cast(MD)) + return V->getValue(); + + // When the value goes to null, it gets replaced by an empty MDNode. + assert(!cast(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 NameTable, StringRef Name) { assert(Name.startswith("llvm.")); diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -4541,6 +4541,15 @@ isa(I) || isa(I) || isa(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(User), + "!DIAssignID should only be used by llvm.dbg.assign intrinsics", + MD, User); + } + } } void Verifier::visitCallStackMetadata(MDNode *MD) { @@ -5023,6 +5032,9 @@ case Intrinsic::dbg_value: // llvm.dbg.value visitDbgIntrinsic("value", cast(Call)); break; + case Intrinsic::dbg_assign: // llvm.dbg.assign + visitDbgIntrinsic("assign", cast(Call)); + break; case Intrinsic::dbg_label: // llvm.dbg.label visitDbgLabelIntrinsic("label", cast(Call)); break; @@ -5986,6 +5998,18 @@ "invalid llvm.dbg." + Kind + " intrinsic expression", &DII, DII.getRawExpression()); + if (auto *DAI = dyn_cast(&DII)) { + CheckDI(isa(DAI->getRawAssignID()), + "invalid llvm.dbg.assign intrinsic DIAssignID", &DII, + DAI->getRawAssignID()); + CheckDI(isa(DAI->getRawAddress()), + "invalid llvm.dbg.assign intrinsic address)", &DII, + DAI->getRawAddress()); + CheckDI(isa(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(N)) diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/roundtrip.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/roundtrip.ll --- a/llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/roundtrip.ll +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/roundtrip.ll @@ -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 @@ !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 --- /dev/null +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/verify.ll @@ -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()