From c37f29c49ea237eaf65dd137bffc1f3316f82951 Mon Sep 17 00:00:00 2001 From: OCHyams Date: Mon, 7 Nov 2022 09:31:45 +0000 Subject: [PATCH] [Assignment Tracking][4/*] Add llvm.dbg.assign intrinsic boilerplate 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 | 65 +++++++++++++-- llvm/include/llvm/IR/Intrinsics.td | 7 ++ llvm/lib/IR/IntrinsicInst.cpp | 41 +++++++++- llvm/lib/IR/Verifier.cpp | 24 ++++++ .../parse-and-verify/roundtrip.ll | 92 ++++++++++++++++++++-- .../assignment-tracking/parse-and-verify/verify.ll | 52 ++++++++++++ 6 files changed, 270 insertions(+), 11 deletions(-) create mode 100644 llvm/test/DebugInfo/Generic/assignment-tracking/parse-and-verify/verify.ll diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index 4ff48c3..f78e45c 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -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(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 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 @@ 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(V) && classof(cast(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(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 index e8fb5c4..3050bd2 100644 --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -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]>; } diff --git a/llvm/lib/IR/IntrinsicInst.cpp b/llvm/lib/IR/IntrinsicInst.cpp index 8ca75f5..b6537b2 100644 --- a/llvm/lib/IR/IntrinsicInst.cpp +++ b/llvm/lib/IR/IntrinsicInst.cpp @@ -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(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 @@ Optional DbgVariableIntrinsic::getFragmentSizeInBits() const { 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 index f0097da..324fa66 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -4541,6 +4541,15 @@ void Verifier::visitDIAssignIDMetadata(Instruction &I, MDNode *MD) { 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 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) { 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 @@ void Verifier::visitDbgIntrinsic(StringRef Kind, DbgVariableIntrinsic &DII) { "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 index 1ddb95b..808636a 100644 --- 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 @@ 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 index 0000000..5772896 --- /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() -- 2.7.4