llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.78k stars 11.9k forks source link

Assertion failure: "conflicting locations for variable" #64149

Open nikic opened 1 year ago

nikic commented 1 year ago

From https://bugzilla.redhat.com/show_bug.cgi?id=2226564:

; RUN: llc < %s
define void @test() !dbg !4 {
  %a1 = alloca { i64, [3 x i64] }, i32 0, align 8
  %a2 = alloca [6 x i8], i32 0, align 2
  call void @llvm.dbg.declare(metadata ptr %a2, metadata !7, metadata !DIExpression(DW_OP_LLVM_fragment, 80, 48)), !dbg !16
  call void @llvm.dbg.declare(metadata ptr %a1, metadata !7, metadata !DIExpression()), !dbg !16
  call void @llvm.memcpy.p0.p0.i64(ptr null, ptr null, i64 1, i1 false), !dbg !18
  ret void
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare void @llvm.dbg.declare(metadata, metadata, metadata) #0

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

!llvm.module.flags = !{!0}
!llvm.dbg.cu = !{!1}

!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !2, producer: "clang LLVM (rustc version 1.73.0-nightly (31395ec38 2023-07-24) (Fedora 1.71.0-2.fc39))", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !3, globals: !3, splitDebugInlining: false)
!2 = !DIFile(filename: "src/cargo/lib.rs/@/cargo.c1756bb5859f0146-cgu.10", directory: "/builddir/build/BUILD/rustc-nightly-src/src/tools/cargo")
!3 = !{}
!4 = distinct !DISubprogram(name: "__deserialize_content<cargo::util::config::de::Deserializer, serde::__private::de::content::ContentVisitor>", linkageName: "_RINvYNtNtNtNtCsgBM6TfnZ7YU_5cargo4util6config2de12DeserializerNtNtCs8ELY4rCNAV8_5serde2de12Deserializer21___deserialize_contentNtNtNtNtB12_9___private2de7content14ContentVisitorEBb_", scope: null, file: !5, line: 1226, type: !6, scopeLine: 1226, flags: DIFlagPrototyped, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !1, templateParams: !3, retainedNodes: !3)
!5 = !DIFile(filename: "/builddir/build/BUILD/rustc-nightly-src/vendor/serde/src/de/mod.rs", directory: "", checksumkind: CSK_MD5, checksum: "fe168d8d4803c3f449894ca7aef5947a")
!6 = distinct !DISubroutineType(types: !3)
!7 = !DILocalVariable(name: "res", scope: !8, file: !9, line: 52, type: !14, align: 8)
!8 = distinct !DILexicalBlock(scope: !10, file: !9, line: 52, column: 13)
!9 = !DIFile(filename: "src/cargo/util/config/de.rs", directory: "/builddir/build/BUILD/rustc-nightly-src/src/tools/cargo", checksumkind: CSK_MD5, checksum: "3faaefd60c37a4d05ff3cc3354e2863b")
!10 = distinct !DILexicalBlock(scope: !11, file: !9, line: 51, column: 30)
!11 = distinct !DILexicalBlock(scope: !12, file: !9, line: 50, column: 9)
!12 = distinct !DISubprogram(name: "deserialize_any<serde::__private::de::content::ContentVisitor>", linkageName: "_RINvXNtNtNtCsgBM6TfnZ7YU_5cargo4util6config2deNtB3_12DeserializerNtNtCs8ELY4rCNAV8_5serde2de12Deserializer15deserialize_anyNtNtNtNtB15_9___private2de7content14ContentVisitorEB9_", scope: null, file: !9, line: 46, type: !13, scopeLine: 46, flags: DIFlagPrototyped, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !1, templateParams: !3, retainedNodes: !3)
!13 = distinct !DISubroutineType(types: !3)
!14 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "(core::result::Result<serde::__private::de::content::Content, cargo::util::config::ConfigError>, cargo::util::config::value::Definition)", file: !15, size: 576, align: 64, elements: !3, templateParams: !3, identifier: "5b7515cf7e437618b1f84245f51f5b4c")
!15 = !DIFile(filename: "<unknown>", directory: "")
!16 = !DILocation(line: 52, column: 17, scope: !8, inlinedAt: !17)
!17 = distinct !DILocation(line: 1234, column: 9, scope: !4)
!18 = !DILocation(line: 62, column: 23, scope: !8, inlinedAt: !17)

Results in:

llc: /home/npopov/repos/llvm-project/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp:318: void llvm::DbgVariable::addMMIEntry(const llvm::DbgVariable&): Assertion `(FrameIndexExprs.size() == 1 || llvm::all_of(FrameIndexExprs, [](FrameIndexExpr &FIE) { return FIE.Expr && FIE.Expr->isFragment(); })) && "conflicting locations for variable"' failed.
[...]
 #7 0x00007ff4180497fc abort /usr/src/debug/glibc-2.36-9.fc37.x86_64/stdlib/abort.c:81:7
 #8 0x00007ff41804971b _nl_load_domain.cold /usr/src/debug/glibc-2.36-9.fc37.x86_64/intl/loadmsgcat.c:1177:9
 #9 0x00007ff418058656 (/lib64/libc.so.6+0x35656)
#10 0x00007ff41d4a5410 llvm::DbgVariable::addMMIEntry(llvm::DbgVariable const&) /home/npopov/repos/llvm-project/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp:308:7
#11 0x00007ff41d4ab54a llvm::DwarfDebug::collectVariableInfoFromMFTable(llvm::DwarfCompileUnit&, llvm::DenseSet<std::pair<llvm::DINode const*, llvm::DILocation const*>, llvm::DenseMapInfo<std::pair<llvm::DINode const*, llvm::DILocation const*>, void>>&) /home/npopov/repos/llvm-project/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp:1581:28
#12 0x00007ff41d4ac896 llvm::DwarfDebug::collectEntityInfo(llvm::DwarfCompileUnit&, llvm::DISubprogram const*, llvm::DenseSet<std::pair<llvm::DINode const*, llvm::DILocation const*>, llvm::DenseMapInfo<std::pair<llvm::DINode const*, llvm::DILocation const*>, void>>&) /home/npopov/repos/llvm-project/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp:1879:24
#13 0x00007ff41d4ae15e llvm::DwarfDebug::endFunctionImpl(llvm::MachineFunction const*) /home/npopov/repos/llvm-project/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp:2291:24
#14 0x00007ff41d45c662 llvm::DebugHandlerBase::endFunction(llvm::MachineFunction const*) /home/npopov/repos/llvm-project/llvm/lib/CodeGen/AsmPrinter/DebugHandlerBase.cpp:412:18
#15 0x00007ff41d3ecd45 llvm::AsmPrinter::emitFunctionBody() /home/npopov/repos/llvm-project/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp:1901:3
llvmbot commented 1 year ago

@llvm/issue-subscribers-debuginfo

jmorse commented 1 year ago

The problem here is the two dbg.declares sort-of overlapping (one describes the location of a field in the source variable, the other describes the location of an entire variable). I don't believe we can have a well defined answer to what that means: variables can't have two locations for any part of them(DWARF can express it, but I don't believe LLVM is designed to handle that). Adding a fragment to the second dbg.declare that overlaps with the first causes a different assertion to fire for similar reasons.

IMO the solution is:

nikic commented 1 year ago

@cuviper Do you happen to still have the original, unreduced bitcode for this?

cuviper commented 1 year ago

I do -- I just added it to bugzilla.

nikic commented 1 year ago

The IR that rustc produces reduces to something like this: https://gist.github.com/nikic/efc3134a0aee3cb9f71601e356c5065b This doesn't trigger an assert by itself, but does after -passes=sroa.

However that input still has two dbg.declares on different allocas with the same DILocalVariable, so I guess that's still an invalid input, just not in a way that crashes llc.

nikic commented 1 year ago

Here's a simple example where rustc assigns the same DILocalVariable to multiple allocas: https://rust.godbolt.org/z/M4cPKhWaE

The IR contains something like this:

  call void @llvm.dbg.declare(metadata ptr %s.dbg.spill, metadata !22, metadata !DIExpression()), !dbg !30
  call void @llvm.dbg.declare(metadata ptr %s.dbg.spill, metadata !23, metadata !DIExpression()), !dbg !31
  call void @llvm.dbg.declare(metadata ptr %s.dbg.spill, metadata !32, metadata !DIExpression()), !dbg !37
  call void @llvm.dbg.declare(metadata ptr %slice.dbg.spill, metadata !23, metadata !DIExpression()), !dbg !31
  call void @llvm.dbg.declare(metadata ptr %slice.dbg.spill, metadata !32, metadata !DIExpression()), !dbg !37

This looks pretty nonsensical to me.

cuviper commented 1 year ago

On the producer side, this is now filed at https://github.com/rust-lang/rust/issues/115113

But on the LLVM side, it would still be nice to catch this in the IR verifier.

nikic commented 1 year ago

@jmorse Could you please clarify what the precise requirements here are? Just having multiple dbg.declares for the same DILocalVariable seems like something that should be fine when inlining is involved, as you can legitimately have multiple copies of the same variable. That behavior exists in clang as well (https://clang.godbolt.org/z/1v1orvxzW with assignment tracking disabled for more direct comparison).

So I'm not entirely clear at which point "multiple overlapping dbg.declares" becomes invalid. Is it just the case where different fragments are involved?

(The debuginfo generated by rustc is wrong in any case, I'm just trying to understand what the verifier check would actually do.)

OCHyams commented 1 year ago

fyi @jmorse is away at the moment.

Just having multiple dbg.declares for the same DILocalVariable seems like something that should be fine when inlining is involved, as you can legitimately have multiple copies of the same variable

In that case the DILocation attached to the debugging intrinsic has an InlinedAt field which is another DILocation that describes the original call site. After inlining that debugging intrinsic a second time its InlinedAt DILocation will have an InlinedAt field too for that inlined call site.

Unlike most DILocations, InlinedAt DILocations are created as distinct (rather than uniqued) metadata.

We generally identify a variable in LLVM as a tuple of the (DILocalVariable, FragmentInfo from the DIExpression, InlinedAt from the DILocation).

nikic commented 1 year ago

We generally identify a variable in LLVM as a tuple of the (DILocalVariable, FragmentInfo from the DIExpression, InlinedAt from the DILocation).

Thanks! This is the bit I was missing.

It looks like it's not possible to verify that there are no repeated DebugVariables, because these may be introduced by transforms that perform code cloning (e.g. LoopRotate, LoopUnswitch, etc). This is discussed in https://github.com/llvm/llvm-project/issues/32504.

So I guess the only thing we can verify is that all fragments for a given (DILocalVariable, InlinedAt) are non-overlapping (excluding exact overlaps).

nikic commented 1 year ago

Testing this patch: https://gist.github.com/nikic/d8c328c4fe13df2d71ac468923b6cec9

Results in a few test failures:

  LLVM :: DebugInfo/AArch64/frameindices.ll
  LLVM :: DebugInfo/X86/pieces-3.ll
  LLVM :: DebugInfo/invalid-overlap.ll
  LLVM :: Transforms/Inline/alloca-dbgdeclare.ll

Annoyingly, this doesn't fail verification on the IR originally produced by rustc, because it doesn't specify fragments, so it looks like the variables are exactly the same. Possibly the way to catch that would be to check whether the size implied by the DILocalVariable is consistent with the size of the alloca.

nikic commented 1 year ago

I've put up https://reviews.llvm.org/D158743 with a sanity check that the fragment can fit into the alloca, but still needs some test fixes.

This doesn't catch the case that actually asserts, but does catch the invalid debuginfo produced by SROA in Rust.

tstellar commented 1 year ago

Has this been fixed in main?

nikic commented 1 year ago

@tstellar The invalid debug info generated by rustc has been fixed, no changes on the LLVM side were necessary. I'm keeping this open to track work on additional verifier checks, which is still ongoing (about to be reverted due to #68929).