From ef24b4b3261fa8f391710cfd73691e97233faaa2 Mon Sep 17 00:00:00 2001 From: Grigory Pastukhov <99913765+grigorypas@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:13:26 -0700 Subject: [PATCH] [Coroutines] Fix debug info scoping for nested structs in coroutine frames (#147622) When generating debug info for coroutine frames, nested struct types were incorrectly inheriting the top-level function scope instead of having their parent struct as scope. This caused assertion failures in DebugInfoMetadata.h during member list replacement for complex nested struct hierarchies. Fix by passing the parent DIStruct as scope when recursively calling solveDIType for nested struct fields, ensuring proper debug info scoping hierarchy. Add regression test that validates proper nested struct scoping hierarchy and prevents future regressions. --- llvm/lib/Transforms/Coroutines/CoroFrame.cpp | 4 +- .../coro-split-dbg-nested-struct.ll | 61 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 llvm/test/Transforms/Coroutines/coro-split-dbg-nested-struct.ll diff --git a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp index 7224a56cd7b8..fe30c6dc6abe 100644 --- a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp +++ b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp @@ -639,10 +639,10 @@ static DIType *solveDIType(DIBuilder &Builder, Type *Ty, SmallVector Elements; for (unsigned I = 0; I < StructTy->getNumElements(); I++) { DIType *DITy = solveDIType(Builder, StructTy->getElementType(I), Layout, - Scope, LineNum, DITypeCache); + DIStruct, LineNum, DITypeCache); assert(DITy); Elements.push_back(Builder.createMemberType( - Scope, DITy->getName(), Scope->getFile(), LineNum, + DIStruct, DITy->getName(), DIStruct->getFile(), LineNum, DITy->getSizeInBits(), DITy->getAlignInBits(), Layout.getStructLayout(StructTy)->getElementOffsetInBits(I), llvm::DINode::FlagArtificial, DITy)); diff --git a/llvm/test/Transforms/Coroutines/coro-split-dbg-nested-struct.ll b/llvm/test/Transforms/Coroutines/coro-split-dbg-nested-struct.ll new file mode 100644 index 000000000000..12dfa1699132 --- /dev/null +++ b/llvm/test/Transforms/Coroutines/coro-split-dbg-nested-struct.ll @@ -0,0 +1,61 @@ +; RUN: opt < %s -passes='cgscc(coro-split)' -S | FileCheck %s + +; Test that nested structs in coroutine frames have correct debug info scoping. + +; Minimal nested struct types that used to trigger a scoping issue: +; we used to set the wrong `scope` for the `DIDerivedType` member entries of the `DICompositeType` +; as well as the `scope` for `DICompositeType` for the inner struct itself. +%"struct.Inner" = type { i32, ptr } +%"struct.Outer" = type { %"struct.Inner", i64 } +%"class.Promise" = type { %"struct.Outer" } + +define void @test_coro_function() presplitcoroutine !dbg !10 { +entry: + %__promise = alloca %"class.Promise", align 8 + %0 = call token @llvm.coro.id(i32 0, ptr %__promise, ptr null, ptr null) + %1 = call ptr @llvm.coro.begin(token %0, ptr null) + %2 = call token @llvm.coro.save(ptr null) + ret void +} + +; CHECK: define void @test_coro_function() + +; Check that frame debug info is generated +; CHECK: ![[FRAME_TYPE:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "{{.*}}.coro_frame_ty" + +; Key validation: Check that nested structs have the correct scope hierarchy +; 1. Promise should be scoped to the frame +; CHECK: ![[PROMISE:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "class_Promise", scope: ![[FRAME_TYPE]] + +; 2. Members of Promise should be scoped to Promise (check this before Outer since it comes first in output) +; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "struct_Outer", scope: ![[PROMISE]] + +; 3. Outer should be scoped to Promise (not the frame!) +; CHECK: ![[OUTER:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "struct_Outer", scope: ![[PROMISE]] + +; 4. First Outer member should be scoped to Outer +; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "struct_Inner", scope: ![[OUTER]] + +; 5. Inner should be scoped to Outer (proper nesting) +; CHECK: ![[INNER:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "struct_Inner", scope: ![[OUTER]] + +; 6. Members of Inner should be scoped to Inner +; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "__int_32", scope: ![[INNER]] +; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "PointerType", scope: ![[INNER]] + +; 7. Second Outer member comes after Inner (due to output order) +; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "__int_64", scope: ![[OUTER]] + +declare token @llvm.coro.id(i32, ptr readnone, ptr readonly, ptr) +declare ptr @llvm.coro.begin(token, ptr writeonly) +declare token @llvm.coro.save(ptr) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!9} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) +!1 = !DIFile(filename: "test.cpp", directory: ".") +!9 = !{i32 2, !"Debug Info Version", i32 3} +!10 = distinct !DISubprogram(name: "test_coro_function", scope: !1, file: !1, line: 1, type: !11, spFlags: DISPFlagDefinition, unit: !0) +!11 = !DISubroutineType(types: !12) +!12 = !{null}