diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h --- a/llvm/include/llvm/IR/DebugInfoMetadata.h +++ b/llvm/include/llvm/IR/DebugInfoMetadata.h @@ -2876,6 +2876,12 @@ return getNumElements() > 0 && getElement(0) == dwarf::DW_OP_LLVM_entry_value; } + + /// Try to shorten an expression with an initial constant operand. + /// Returns a new expression and constant on success, or the original + /// expression and constant on failure. + std::pair + constantFold(const ConstantInt *CI); }; inline bool operator==(const DIExpression::FragmentInfo &A, diff --git a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp --- a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp @@ -1292,18 +1292,22 @@ BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DbgLoc, II, false, 0U, DI->getVariable(), DI->getExpression()); } else if (const auto *CI = dyn_cast(V)) { + // See if there's an expression to constant-fold. + DIExpression *Expr = DI->getExpression(); + if (Expr) + std::tie(Expr, CI) = Expr->constantFold(CI); if (CI->getBitWidth() > 64) BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DbgLoc, II) .addCImm(CI) .addImm(0U) .addMetadata(DI->getVariable()) - .addMetadata(DI->getExpression()); + .addMetadata(Expr); else BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DbgLoc, II) .addImm(CI->getZExtValue()) .addImm(0U) .addMetadata(DI->getVariable()) - .addMetadata(DI->getExpression()); + .addMetadata(Expr); } else if (const auto *CF = dyn_cast(V)) { BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DbgLoc, II) .addFPImm(CF) diff --git a/llvm/lib/CodeGen/SelectionDAG/InstrEmitter.cpp b/llvm/lib/CodeGen/SelectionDAG/InstrEmitter.cpp --- a/llvm/lib/CodeGen/SelectionDAG/InstrEmitter.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/InstrEmitter.cpp @@ -872,17 +872,28 @@ InstrEmitter::EmitDbgValueFromSingleOp(SDDbgValue *SD, DenseMap &VRBaseMap) { MDNode *Var = SD->getVariable(); - MDNode *Expr = SD->getExpression(); + DIExpression *Expr = SD->getExpression(); DebugLoc DL = SD->getDebugLoc(); const MCInstrDesc &II = TII->get(TargetOpcode::DBG_VALUE); assert(SD->getLocationOps().size() == 1 && "Non variadic dbg_value should have only one location op"); + // See about constant-folding the expression. + // Copy the location operand in case we replace it. + SmallVector LocationOps(1, SD->getLocationOps()[0]); + if (Expr && LocationOps[0].getKind() == SDDbgOperand::CONST) { + const Value *V = LocationOps[0].getConst(); + if (auto *C = dyn_cast(V)) { + std::tie(Expr, C) = Expr->constantFold(C); + LocationOps[0] = SDDbgOperand::fromConst(C); + } + } + // Emit non-variadic dbg_value nodes as DBG_VALUE. // DBG_VALUE := "DBG_VALUE" loc, isIndirect, var, expr auto MIB = BuildMI(*MF, DL, II); - AddDbgValueLocationOps(MIB, II, SD->getLocationOps(), VRBaseMap); + AddDbgValueLocationOps(MIB, II, LocationOps, VRBaseMap); if (SD->isIndirect()) MIB.addImm(0U); diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp --- a/llvm/lib/IR/DebugInfoMetadata.cpp +++ b/llvm/lib/IR/DebugInfoMetadata.cpp @@ -1476,6 +1476,45 @@ return DIExpression::get(Expr->getContext(), Ops); } +std::pair +DIExpression::constantFold(const ConstantInt *CI) { + // Copy the APInt so we can modify it. + APInt NewInt = CI->getValue(); + SmallVector Ops; + + // Fold operators only at the beginning of the expression. + bool First = true; + bool Changed = false; + for (auto Op : expr_ops()) { + switch (Op.getOp()) { + default: + // We fold only the leading part of the expression; if we get to a part + // that we're going to copy unchanged, and haven't done any folding, + // then the entire expression is unchanged and we can return early. + if (!Changed) + return {this, CI}; + First = false; + break; + case dwarf::DW_OP_LLVM_convert: + if (!First) + break; + Changed = true; + if (Op.getArg(1) == dwarf::DW_ATE_signed) + NewInt = NewInt.sextOrTrunc(Op.getArg(0)); + else { + assert(Op.getArg(1) == dwarf::DW_ATE_unsigned && "Unexpected operand"); + NewInt = NewInt.zextOrTrunc(Op.getArg(0)); + } + continue; + } + Op.appendToVector(Ops); + } + if (!Changed) + return {this, CI}; + return {DIExpression::get(getContext(), Ops), + ConstantInt::get(getContext(), NewInt)}; +} + uint64_t DIExpression::getNumLocationOperands() const { uint64_t Result = 0; for (auto ExprOp : expr_ops()) diff --git a/llvm/test/DebugInfo/X86/DIExpr-const-folding.ll b/llvm/test/DebugInfo/X86/DIExpr-const-folding.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/X86/DIExpr-const-folding.ll @@ -0,0 +1,95 @@ +; RUN: llc -mtriple=x86_64 -filetype=obj < %s \ +; RUN: | llvm-dwarfdump -debug-info - | FileCheck %s +; RUN: llc -mtriple=x86_64 -filetype=obj -fast-isel < %s \ +; RUN: | llvm-dwarfdump -debug-info - | FileCheck %s + +;; The important thing is the DW_OP_lit2 with no converts. +;; TODO: Make this work with global isel +;; Indirectly related FIXME: Should be able to emit DW_AT_const_value instead. + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_location (DW_OP_lit2, DW_OP_stack_value) +; CHECK-NEXT: DW_AT_name ("bIsShowingCollision") + +%class.UClient = type { %class.UWorld*, %struct.FFlags } +%class.UWorld = type { i16 } +%struct.FFlags = type { [9 x i8], i32 } + +define dso_local void @_ZN7UClient13ToggleVolumesEv(%class.UClient* nocapture nonnull align 8 dereferenceable(24) %this) local_unnamed_addr align 2 !dbg !8 { +entry: + call void @llvm.dbg.value(metadata i72 2, metadata !43, metadata !DIExpression(DW_OP_LLVM_convert, 72, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value)), !dbg !48 + %World = getelementptr inbounds %class.UClient, %class.UClient* %this, i64 0, i32 0, !dbg !49 + %0 = load %class.UWorld*, %class.UWorld** %World, align 8, !dbg !49, !tbaa !51 + %1 = getelementptr %class.UWorld, %class.UWorld* %0, i64 0, i32 0, !dbg !58 + store i16 2, i16* %1, align 1, !dbg !59 + ret void, !dbg !60 +} + +; Function Attrs: nofree nosync nounwind readnone speculatable willreturn +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5, !6} +!llvm.ident = !{!7} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 13.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "min-test-3.cpp", directory: "/home/probinson/projects/scratch/tc8251") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{i32 7, !"uwtable", i32 1} +!7 = !{!"clang version 13.0.0"} +!8 = distinct !DISubprogram(name: "ToggleVolumes", linkageName: "_ZN7UClient13ToggleVolumesEv", scope: !9, file: !1, line: 39, type: !37, scopeLine: 40, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, declaration: !36, retainedNodes: !40) +!9 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "UClient", file: !1, line: 31, size: 192, flags: DIFlagTypePassByValue | DIFlagNonTrivial, elements: !10, identifier: "_ZTS7UClient") +!10 = !{!11, !20, !36} +!11 = !DIDerivedType(tag: DW_TAG_member, name: "World", scope: !9, file: !1, line: 34, baseType: !12, size: 64, flags: DIFlagPublic) +!12 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !13, size: 64) +!13 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "UWorld", file: !1, line: 8, size: 16, flags: DIFlagTypePassByValue, elements: !14, identifier: "_ZTS6UWorld") +!14 = !{!15, !18, !19} +!15 = !DIDerivedType(tag: DW_TAG_member, name: "bCollision", scope: !13, file: !1, line: 11, baseType: !16, size: 1, flags: DIFlagPublic | DIFlagBitField, extraData: i64 0) +!16 = !DIDerivedType(tag: DW_TAG_typedef, name: "uint8", file: !1, line: 1, baseType: !17) +!17 = !DIBasicType(name: "unsigned char", size: 8, encoding: DW_ATE_unsigned_char) +!18 = !DIDerivedType(tag: DW_TAG_member, name: "dummyA", scope: !13, file: !1, line: 12, baseType: !16, size: 7, offset: 1, flags: DIFlagPublic | DIFlagBitField, extraData: i64 0) +!19 = !DIDerivedType(tag: DW_TAG_member, name: "dummyB", scope: !13, file: !1, line: 13, baseType: !16, size: 1, offset: 8, flags: DIFlagPublic | DIFlagBitField, extraData: i64 0) +!20 = !DIDerivedType(tag: DW_TAG_member, name: "EngineShowFlags", scope: !9, file: !1, line: 35, baseType: !21, size: 128, offset: 64, flags: DIFlagPublic) +!21 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "FFlags", file: !1, line: 16, size: 128, flags: DIFlagTypePassByValue | DIFlagNonTrivial, elements: !22, identifier: "_ZTS6FFlags") +!22 = !{!23, !26, !27, !28, !29, !30, !32} +!23 = !DIDerivedType(tag: DW_TAG_member, name: "Volumes", scope: !21, file: !1, line: 18, baseType: !24, size: 1, flags: DIFlagBitField, extraData: i64 0) +!24 = !DIDerivedType(tag: DW_TAG_typedef, name: "uint32", file: !1, line: 2, baseType: !25) +!25 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned) +!26 = !DIDerivedType(tag: DW_TAG_member, name: "Collision", scope: !21, file: !1, line: 19, baseType: !24, size: 1, offset: 1, flags: DIFlagBitField, extraData: i64 0) +!27 = !DIDerivedType(tag: DW_TAG_member, name: "dummy1", scope: !21, file: !1, line: 20, baseType: !24, size: 30, offset: 2, flags: DIFlagBitField, extraData: i64 0) +!28 = !DIDerivedType(tag: DW_TAG_member, name: "dummy2", scope: !21, file: !1, line: 21, baseType: !24, size: 32, offset: 32, flags: DIFlagBitField, extraData: i64 0) +!29 = !DIDerivedType(tag: DW_TAG_member, name: "dummy3", scope: !21, file: !1, line: 22, baseType: !24, size: 1, offset: 64, flags: DIFlagBitField, extraData: i64 0) +!30 = !DIDerivedType(tag: DW_TAG_member, name: "CustomShowFlags", scope: !21, file: !1, line: 24, baseType: !31, size: 32, offset: 96) +!31 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!32 = !DISubprogram(name: "FFlags", scope: !21, file: !1, line: 25, type: !33, scopeLine: 25, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized) +!33 = !DISubroutineType(types: !34) +!34 = !{null, !35} +!35 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !21, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer) +!36 = !DISubprogram(name: "ToggleVolumes", linkageName: "_ZN7UClient13ToggleVolumesEv", scope: !9, file: !1, line: 36, type: !37, scopeLine: 36, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized) +!37 = !DISubroutineType(types: !38) +!38 = !{null, !39} +!39 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !9, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer) +!40 = !{!41, !43} +!41 = !DILocalVariable(name: "this", arg: 1, scope: !8, type: !42, flags: DIFlagArtificial | DIFlagObjectPointer) +!42 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !9, size: 64) +!43 = !DILocalVariable(name: "bIsShowingCollision", scope: !44, file: !1, line: 45, type: !46) +!44 = distinct !DILexicalBlock(scope: !45, file: !1, line: 42, column: 2) +!45 = distinct !DILexicalBlock(scope: !8, file: !1, line: 41, column: 6) +!46 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !47) +!47 = !DIBasicType(name: "bool", size: 8, encoding: DW_ATE_boolean) +!48 = !DILocation(line: 0, scope: !44) +!49 = !DILocation(line: 51, column: 8, scope: !50) +!50 = distinct !DILexicalBlock(scope: !44, file: !1, line: 51, column: 8) +!51 = !{!52, !53, i64 0} +!52 = !{!"_ZTS7UClient", !53, i64 0, !56, i64 8} +!53 = !{!"any pointer", !54, i64 0} +!54 = !{!"omnipotent char", !55, i64 0} +!55 = !{!"Simple C++ TBAA"} +!56 = !{!"_ZTS6FFlags", !57, i64 0, !57, i64 0, !57, i64 0, !57, i64 4, !57, i64 8, !57, i64 12} +!57 = !{!"int", !54, i64 0} +!58 = !DILocation(line: 52, column: 12, scope: !50) +!59 = !DILocation(line: 52, column: 23, scope: !50) +!60 = !DILocation(line: 55, column: 1, scope: !8) diff --git a/llvm/test/DebugInfo/X86/convert-debugloc.ll b/llvm/test/DebugInfo/X86/convert-debugloc.ll --- a/llvm/test/DebugInfo/X86/convert-debugloc.ll +++ b/llvm/test/DebugInfo/X86/convert-debugloc.ll @@ -27,7 +27,7 @@ ; RUN: | FileCheck %s --check-prefix=VERBOSE --check-prefix=CONV "--implicit-check-not={{DW_TAG|NULL}}" -; SPLITCONV: Compile Unit:{{.*}} DWO_id = 0xe91d8d1d7f9782c0 +; SPLITCONV: Compile Unit:{{.*}} DWO_id = 0x62f17241069b1fa3 ; SPLIT: DW_TAG_skeleton_unit ; CONV: DW_TAG_compile_unit @@ -44,7 +44,7 @@ ; CONV: DW_TAG_subprogram ; CONV: DW_TAG_formal_parameter ; CONV: DW_TAG_variable -; CONV: DW_AT_location {{.*}}DW_OP_constu 0x20, DW_OP_convert ( +; CONV: DW_AT_location {{.*}}DW_OP_constu 0x20, DW_OP_lit0, DW_OP_plus, DW_OP_convert ( ; VERBOSE-SAME: [[SIG8]] -> ; CONV-SAME: [[SIG8]]) "DW_ATE_signed_8", DW_OP_convert ( ; VERBOSE-SAME: [[SIG32]] -> @@ -76,7 +76,11 @@ define dso_local signext i8 @foo(i8 signext %x) !dbg !7 { entry: call void @llvm.dbg.value(metadata i8 %x, metadata !11, metadata !DIExpression()), !dbg !12 - call void @llvm.dbg.value(metadata i8 32, metadata !13, metadata !DIExpression(DW_OP_LLVM_convert, 8, DW_ATE_signed, DW_OP_LLVM_convert, 32, DW_ATE_signed, DW_OP_stack_value)), !dbg !15 +;; This test depends on "convert" surviving all the way to the final object. +;; So, insert something before DW_OP_LLVM_convert that the expression folder +;; will not attempt to eliminate. At the moment, only "convert" ops are folded. +;; If you have to change the expression, the expected DWO_id also changes. + call void @llvm.dbg.value(metadata i8 32, metadata !13, metadata !DIExpression(DW_OP_lit0, DW_OP_plus, DW_OP_LLVM_convert, 8, DW_ATE_signed, DW_OP_LLVM_convert, 32, DW_ATE_signed, DW_OP_stack_value)), !dbg !15 ret i8 %x, !dbg !16 } diff --git a/llvm/test/DebugInfo/X86/convert-linked.ll b/llvm/test/DebugInfo/X86/convert-linked.ll --- a/llvm/test/DebugInfo/X86/convert-linked.ll +++ b/llvm/test/DebugInfo/X86/convert-linked.ll @@ -19,14 +19,20 @@ define dso_local signext i8 @foo(i8 signext %x) !dbg !9 { entry: call void @llvm.dbg.value(metadata i8 %x, metadata !13, metadata !DIExpression()), !dbg !14 - call void @llvm.dbg.value(metadata i8 32, metadata !15, metadata !DIExpression(DW_OP_LLVM_convert, 8, DW_ATE_signed, DW_OP_LLVM_convert, 32, DW_ATE_signed, DW_OP_stack_value)), !dbg !17 +;; This test depends on "convert" surviving all the way to the final object. +;; So, insert something before DW_OP_LLVM_convert that the expression folder +;; will not attempt to eliminate. At the moment, only "convert" ops are folded. + call void @llvm.dbg.value(metadata i8 32, metadata !15, metadata !DIExpression(DW_OP_lit0, DW_OP_plus, DW_OP_LLVM_convert, 8, DW_ATE_signed, DW_OP_LLVM_convert, 32, DW_ATE_signed, DW_OP_stack_value)), !dbg !17 ret i8 %x, !dbg !18 } define dso_local signext i8 @bar(i8 signext %x) !dbg !19 { entry: call void @llvm.dbg.value(metadata i8 %x, metadata !20, metadata !DIExpression()), !dbg !21 - call void @llvm.dbg.value(metadata i8 32, metadata !22, metadata !DIExpression(DW_OP_LLVM_convert, 8, DW_ATE_signed, DW_OP_LLVM_convert, 16, DW_ATE_signed, DW_OP_stack_value)), !dbg !24 +;; This test depends on "convert" surviving all the way to the final object. +;; So, insert something before DW_OP_LLVM_convert that the expression folder +;; will not attempt to eliminate. At the moment, only "convert" ops are folded. + call void @llvm.dbg.value(metadata i8 32, metadata !22, metadata !DIExpression(DW_OP_lit0, DW_OP_plus, DW_OP_LLVM_convert, 8, DW_ATE_signed, DW_OP_LLVM_convert, 16, DW_ATE_signed, DW_OP_stack_value)), !dbg !24 ret i8 %x, !dbg !25 } diff --git a/llvm/test/DebugInfo/X86/convert-loclist.ll b/llvm/test/DebugInfo/X86/convert-loclist.ll --- a/llvm/test/DebugInfo/X86/convert-loclist.ll +++ b/llvm/test/DebugInfo/X86/convert-loclist.ll @@ -13,7 +13,7 @@ ; often - add another IR file with a different DW_OP_convert that's otherwise ; identical and demonstrate that they have different DWO IDs. -; SPLIT: 0x00000000: Compile Unit: {{.*}} DWO_id = 0xafd73565c68bc661 +; SPLIT: 0x00000000: Compile Unit: {{.*}} DWO_id = 0xecf2563326b0bdd3 ; Regression testing a fairly quirky bug where instead of hashing (see above), ; extra bytes would be emitted into the output assembly in no @@ -23,13 +23,17 @@ ; CHECK: 0x{{0*}}[[TYPE:.*]]: DW_TAG_base_type ; CHECK-NEXT: DW_AT_name ("DW_ATE_unsigned_32") -; CHECK: DW_LLE_offset_pair ({{.*}}): DW_OP_consts +7, DW_OP_convert 0x[[TYPE]], DW_OP_stack_value +; CHECK: DW_LLE_offset_pair ({{.*}}): DW_OP_consts +7, DW_OP_lit0, DW_OP_plus, DW_OP_convert 0x[[TYPE]], DW_OP_stack_value ; Function Attrs: uwtable define dso_local void @_Z2f2v() local_unnamed_addr #0 !dbg !11 { entry: tail call void @_Z2f1v(), !dbg !15 - call void @llvm.dbg.value(metadata i32 7, metadata !13, metadata !DIExpression(DW_OP_LLVM_convert, 32, DW_ATE_unsigned, DW_OP_stack_value)), !dbg !16 +;; This test depends on "convert" surviving all the way to the final object. +;; So, insert something before DW_OP_LLVM_convert that the expression folder +;; will not attempt to eliminate. At the moment, only "convert" ops are folded. +;; If you have to change the expression, the expected DWO_id also changes. + call void @llvm.dbg.value(metadata i32 7, metadata !13, metadata !DIExpression(DW_OP_lit0, DW_OP_plus, DW_OP_LLVM_convert, 32, DW_ATE_unsigned, DW_OP_stack_value)), !dbg !16 tail call void @_Z2f1v(), !dbg !17 ret void, !dbg !18 } diff --git a/llvm/unittests/IR/MetadataTest.cpp b/llvm/unittests/IR/MetadataTest.cpp --- a/llvm/unittests/IR/MetadataTest.cpp +++ b/llvm/unittests/IR/MetadataTest.cpp @@ -2894,6 +2894,55 @@ #undef EXPECT_REPLACE_ARG_EQ } +TEST_F(DIExpressionTest, foldConstant) { + const ConstantInt *Int; + const ConstantInt *NewInt; + DIExpression *Expr; + DIExpression *NewExpr; + +#define EXPECT_FOLD_CONST(StartWidth, StartValue, EndWidth, EndValue, NumElts) \ + Int = ConstantInt::get(Context, APInt(StartWidth, StartValue)); \ + std::tie(NewExpr, NewInt) = Expr->constantFold(Int); \ + ASSERT_EQ(NewInt->getBitWidth(), EndWidth##u); \ + EXPECT_EQ(NewInt->getValue(), APInt(EndWidth, EndValue)); \ + EXPECT_EQ(NewExpr->getNumElements(), NumElts##u) + + // Unfoldable expression should return the original unmodified Int/Expr. + Expr = DIExpression::get(Context, {dwarf::DW_OP_deref}); + EXPECT_FOLD_CONST(32, 117, 32, 117, 1); + EXPECT_EQ(NewExpr, Expr); + EXPECT_EQ(NewInt, Int); + EXPECT_TRUE(NewExpr->startsWithDeref()); + + // One unsigned bit-width conversion. + Expr = DIExpression::get( + Context, {dwarf::DW_OP_LLVM_convert, 72, dwarf::DW_ATE_unsigned}); + EXPECT_FOLD_CONST(8, 12, 72, 12, 0); + + // Two unsigned bit-width conversions (mask truncation). + Expr = DIExpression::get( + Context, {dwarf::DW_OP_LLVM_convert, 8, dwarf::DW_ATE_unsigned, + dwarf::DW_OP_LLVM_convert, 16, dwarf::DW_ATE_unsigned}); + EXPECT_FOLD_CONST(32, -1, 16, 0xff, 0); + + // Sign extension. + Expr = DIExpression::get( + Context, {dwarf::DW_OP_LLVM_convert, 32, dwarf::DW_ATE_signed}); + EXPECT_FOLD_CONST(16, -1, 32, -1, 0); + + // Get non-foldable operations back in the new Expr. + uint64_t Elements[] = {dwarf::DW_OP_deref, dwarf::DW_OP_stack_value}; + ArrayRef Expected = Elements; + Expr = DIExpression::get( + Context, {dwarf::DW_OP_LLVM_convert, 32, dwarf::DW_ATE_signed}); + Expr = DIExpression::append(Expr, Expected); + ASSERT_EQ(Expr->getNumElements(), 5u); + EXPECT_FOLD_CONST(16, -1, 32, -1, 2); + EXPECT_EQ(NewExpr->getElements(), Expected); + +#undef EXPECT_FOLD_CONST +} + typedef MetadataTest DIObjCPropertyTest; TEST_F(DIObjCPropertyTest, get) {