Skip to content

Commit ef24b4b

Browse files
authored
[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.
1 parent 4b6e54a commit ef24b4b

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,10 +639,10 @@ static DIType *solveDIType(DIBuilder &Builder, Type *Ty,
639639
SmallVector<Metadata *, 16> Elements;
640640
for (unsigned I = 0; I < StructTy->getNumElements(); I++) {
641641
DIType *DITy = solveDIType(Builder, StructTy->getElementType(I), Layout,
642-
Scope, LineNum, DITypeCache);
642+
DIStruct, LineNum, DITypeCache);
643643
assert(DITy);
644644
Elements.push_back(Builder.createMemberType(
645-
Scope, DITy->getName(), Scope->getFile(), LineNum,
645+
DIStruct, DITy->getName(), DIStruct->getFile(), LineNum,
646646
DITy->getSizeInBits(), DITy->getAlignInBits(),
647647
Layout.getStructLayout(StructTy)->getElementOffsetInBits(I),
648648
llvm::DINode::FlagArtificial, DITy));
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
; RUN: opt < %s -passes='cgscc(coro-split)' -S | FileCheck %s
2+
3+
; Test that nested structs in coroutine frames have correct debug info scoping.
4+
5+
; Minimal nested struct types that used to trigger a scoping issue:
6+
; we used to set the wrong `scope` for the `DIDerivedType` member entries of the `DICompositeType`
7+
; as well as the `scope` for `DICompositeType` for the inner struct itself.
8+
%"struct.Inner" = type { i32, ptr }
9+
%"struct.Outer" = type { %"struct.Inner", i64 }
10+
%"class.Promise" = type { %"struct.Outer" }
11+
12+
define void @test_coro_function() presplitcoroutine !dbg !10 {
13+
entry:
14+
%__promise = alloca %"class.Promise", align 8
15+
%0 = call token @llvm.coro.id(i32 0, ptr %__promise, ptr null, ptr null)
16+
%1 = call ptr @llvm.coro.begin(token %0, ptr null)
17+
%2 = call token @llvm.coro.save(ptr null)
18+
ret void
19+
}
20+
21+
; CHECK: define void @test_coro_function()
22+
23+
; Check that frame debug info is generated
24+
; CHECK: ![[FRAME_TYPE:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "{{.*}}.coro_frame_ty"
25+
26+
; Key validation: Check that nested structs have the correct scope hierarchy
27+
; 1. Promise should be scoped to the frame
28+
; CHECK: ![[PROMISE:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "class_Promise", scope: ![[FRAME_TYPE]]
29+
30+
; 2. Members of Promise should be scoped to Promise (check this before Outer since it comes first in output)
31+
; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "struct_Outer", scope: ![[PROMISE]]
32+
33+
; 3. Outer should be scoped to Promise (not the frame!)
34+
; CHECK: ![[OUTER:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "struct_Outer", scope: ![[PROMISE]]
35+
36+
; 4. First Outer member should be scoped to Outer
37+
; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "struct_Inner", scope: ![[OUTER]]
38+
39+
; 5. Inner should be scoped to Outer (proper nesting)
40+
; CHECK: ![[INNER:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "struct_Inner", scope: ![[OUTER]]
41+
42+
; 6. Members of Inner should be scoped to Inner
43+
; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "__int_32", scope: ![[INNER]]
44+
; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "PointerType", scope: ![[INNER]]
45+
46+
; 7. Second Outer member comes after Inner (due to output order)
47+
; CHECK: !DIDerivedType(tag: DW_TAG_member, name: "__int_64", scope: ![[OUTER]]
48+
49+
declare token @llvm.coro.id(i32, ptr readnone, ptr readonly, ptr)
50+
declare ptr @llvm.coro.begin(token, ptr writeonly)
51+
declare token @llvm.coro.save(ptr)
52+
53+
!llvm.dbg.cu = !{!0}
54+
!llvm.module.flags = !{!9}
55+
56+
!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
57+
!1 = !DIFile(filename: "test.cpp", directory: ".")
58+
!9 = !{i32 2, !"Debug Info Version", i32 3}
59+
!10 = distinct !DISubprogram(name: "test_coro_function", scope: !1, file: !1, line: 1, type: !11, spFlags: DISPFlagDefinition, unit: !0)
60+
!11 = !DISubroutineType(types: !12)
61+
!12 = !{null}

0 commit comments

Comments
 (0)