Skip to content

Commit f8f5a5f

Browse files
andykaylorrorth
authored andcommitted
[CIR] Add support for completing forward-declared types (llvm#143176)
This adds the needed handling for completing record types which were previously declared leading us to create an incomplete record type.
1 parent 733b81a commit f8f5a5f

File tree

10 files changed

+221
-3
lines changed

10 files changed

+221
-3
lines changed

clang/include/clang/CIR/CIRGenerator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class CIRGenerator : public clang::ASTConsumer {
7878
bool HandleTopLevelDecl(clang::DeclGroupRef group) override;
7979
void HandleTranslationUnit(clang::ASTContext &astContext) override;
8080
void HandleInlineFunctionDefinition(clang::FunctionDecl *d) override;
81+
void HandleTagDeclDefinition(clang::TagDecl *d) override;
8182
void CompleteTentativeDefinition(clang::VarDecl *d) override;
8283

8384
mlir::ModuleOp getModule() const;

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ struct MissingFeatures {
199199
static bool msabi() { return false; }
200200
static bool typeChecks() { return false; }
201201
static bool lambdaFieldToName() { return false; }
202-
static bool updateCompletedType() { return false; }
203202
static bool moduleNameHash() { return false; }
204203
static bool constantFoldSwitchStatement() { return false; }
205204
static bool cudaSupport() { return false; }

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,11 @@ void CIRGenModule::maybeSetTrivialComdat(const Decl &d, mlir::Operation *op) {
838838
assert(!cir::MissingFeatures::opFuncSetComdat());
839839
}
840840

841+
void CIRGenModule::updateCompletedType(const TagDecl *td) {
842+
// Make sure that this type is translated.
843+
genTypes.updateCompletedType(td);
844+
}
845+
841846
// TODO(CIR): this could be a common method between LLVM codegen.
842847
static bool isVarDeclStrongDefinition(const ASTContext &astContext,
843848
CIRGenModule &cgm, const VarDecl *vd,
@@ -1145,6 +1150,7 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
11451150

11461151
// No code generation needed.
11471152
case Decl::UsingShadow:
1153+
case Decl::Empty:
11481154
break;
11491155

11501156
// C++ Decls

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ class CIRGenModule : public CIRGenTypeCache {
241241

242242
void emitTentativeDefinition(const VarDecl *d);
243243

244+
// Make sure that this type is translated.
245+
void updateCompletedType(const clang::TagDecl *td);
246+
244247
bool supportsCOMDAT() const;
245248
void maybeSetTrivialComdat(const clang::Decl &d, mlir::Operation *op);
246249

clang/lib/CIR/CodeGen/CIRGenTypes.cpp

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,6 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
432432
}
433433

434434
case Type::Enum: {
435-
// TODO(cir): Implement updateCompletedType for enums.
436-
assert(!cir::MissingFeatures::updateCompletedType());
437435
const EnumDecl *ED = cast<EnumType>(ty)->getDecl();
438436
if (auto integerType = ED->getIntegerType(); !integerType.isNull())
439437
return convertType(integerType);
@@ -586,3 +584,40 @@ const CIRGenFunctionInfo &CIRGenTypes::arrangeGlobalDeclaration(GlobalDecl gd) {
586584

587585
return arrangeFunctionDeclaration(fd);
588586
}
587+
588+
// When we find the full definition for a TagDecl, replace the 'opaque' type we
589+
// previously made for it if applicable.
590+
void CIRGenTypes::updateCompletedType(const TagDecl *td) {
591+
// If this is an enum being completed, then we flush all non-struct types
592+
// from the cache. This allows function types and other things that may be
593+
// derived from the enum to be recomputed.
594+
if (const auto *ed = dyn_cast<EnumDecl>(td)) {
595+
// Classic codegen clears the type cache if it contains an entry for this
596+
// enum type that doesn't use i32 as the underlying type, but I can't find
597+
// a test case that meets that condition. C++ doesn't allow forward
598+
// declaration of enums, and C doesn't allow an incomplete forward
599+
// declaration with a non-default type.
600+
assert(
601+
!typeCache.count(ed->getTypeForDecl()) ||
602+
(convertType(ed->getIntegerType()) == typeCache[ed->getTypeForDecl()]));
603+
// If necessary, provide the full definition of a type only used with a
604+
// declaration so far.
605+
assert(!cir::MissingFeatures::generateDebugInfo());
606+
return;
607+
}
608+
609+
// If we completed a RecordDecl that we previously used and converted to an
610+
// anonymous type, then go ahead and complete it now.
611+
const auto *rd = cast<RecordDecl>(td);
612+
if (rd->isDependentType())
613+
return;
614+
615+
// Only complete if we converted it already. If we haven't converted it yet,
616+
// we'll just do it lazily.
617+
if (recordDeclTypes.count(astContext.getTagDeclType(rd).getTypePtr()))
618+
convertRecordDeclType(rd);
619+
620+
// If necessary, provide the full definition of a type only used with a
621+
// declaration so far.
622+
assert(!cir::MissingFeatures::generateDebugInfo());
623+
}

clang/lib/CIR/CodeGen/CIRGenTypes.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ class CIRGenTypes {
151151

152152
const CIRGenFunctionInfo &arrangeGlobalDeclaration(GlobalDecl gd);
153153

154+
/// UpdateCompletedType - when we find the full definition for a TagDecl,
155+
/// replace the 'opaque' type we previously made for it if applicable.
156+
void updateCompletedType(const clang::TagDecl *td);
157+
154158
/// Free functions are functions that are compatible with an ordinary C
155159
/// function pointer type.
156160
const CIRGenFunctionInfo &

clang/lib/CIR/CodeGen/CIRGenerator.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,29 @@ void CIRGenerator::emitDeferredDecls() {
129129
deferredInlineMemberFuncDefs.clear();
130130
}
131131

132+
/// HandleTagDeclDefinition - This callback is invoked each time a TagDecl to
133+
/// (e.g. struct, union, enum, class) is completed. This allows the client to
134+
/// hack on the type, which can occur at any point in the file (because these
135+
/// can be defined in declspecs).
136+
void CIRGenerator::HandleTagDeclDefinition(TagDecl *d) {
137+
if (diags.hasErrorOccurred())
138+
return;
139+
140+
// Don't allow re-entrant calls to CIRGen triggered by PCH deserialization to
141+
// emit deferred decls.
142+
HandlingTopLevelDeclRAII handlingDecl(*this, /*EmitDeferred=*/false);
143+
144+
cgm->updateCompletedType(d);
145+
146+
// For MSVC compatibility, treat declarations of static data members with
147+
// inline initializers as definitions.
148+
if (astContext->getTargetInfo().getCXXABI().isMicrosoft())
149+
cgm->errorNYI(d->getSourceRange(), "HandleTagDeclDefinition: MSABI");
150+
// For OpenMP emit declare reduction functions, if required.
151+
if (astContext->getLangOpts().OpenMP)
152+
cgm->errorNYI(d->getSourceRange(), "HandleTagDeclDefinition: OpenMP");
153+
}
154+
132155
void CIRGenerator::CompleteTentativeDefinition(VarDecl *d) {
133156
if (diags.hasErrorOccurred())
134157
return;

clang/lib/CIR/FrontendAction/CIRGenAction.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ class CIRGenConsumer : public clang::ASTConsumer {
140140
}
141141
}
142142

143+
void HandleTagDeclDefinition(TagDecl *D) override {
144+
PrettyStackTraceDecl CrashInfo(D, SourceLocation(),
145+
Context->getSourceManager(),
146+
"CIR generation of declaration");
147+
Gen->HandleTagDeclDefinition(D);
148+
}
149+
143150
void CompleteTentativeDefinition(VarDecl *D) override {
144151
Gen->CompleteTentativeDefinition(D);
145152
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// RUN: split-file %s %t
2+
3+
4+
//--- incomplete_struct
5+
6+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %t/incomplete_struct -o %t/incomplete_struct.cir
7+
// RUN: FileCheck %s --input-file=%t/incomplete_struct.cir --check-prefix=CHECK1
8+
9+
// Forward declaration of the record is never defined, so it is created as
10+
// an incomplete struct in CIR and will remain as such.
11+
12+
// CHECK1: ![[INC_STRUCT:.+]] = !cir.record<struct "IncompleteStruct" incomplete>
13+
struct IncompleteStruct;
14+
// CHECK1: testIncompleteStruct(%arg0: !cir.ptr<![[INC_STRUCT]]>
15+
void testIncompleteStruct(struct IncompleteStruct *s) {};
16+
17+
18+
19+
//--- mutated_struct
20+
21+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %t/mutated_struct -o %t/mutated_struct.cir
22+
// RUN: FileCheck %s --input-file=%t/mutated_struct.cir --check-prefix=CHECK2
23+
24+
// Foward declaration of the struct is followed by usage, then definition.
25+
// This means it will initially be created as incomplete, then completed.
26+
27+
// CHECK2: ![[COMPLETE:.+]] = !cir.record<struct "ForwardDeclaredStruct" {!s32i}>
28+
// CHECK2: testForwardDeclaredStruct(%arg0: !cir.ptr<![[COMPLETE]]>
29+
struct ForwardDeclaredStruct;
30+
void testForwardDeclaredStruct(struct ForwardDeclaredStruct *fds) {};
31+
struct ForwardDeclaredStruct {
32+
int testVal;
33+
};
34+
35+
36+
37+
//--- recursive_struct
38+
39+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %t/recursive_struct -o %t/recursive_struct.cir
40+
// RUN: FileCheck --check-prefix=CHECK3 --input-file=%t/recursive_struct.cir %s
41+
42+
// Struct is initially forward declared since the self-reference is generated
43+
// first. Then, once the type is fully generated, it is completed.
44+
45+
// CHECK3: ![[STRUCT:.+]] = !cir.record<struct "RecursiveStruct" {!s32i, !cir.ptr<!cir.record<struct "RecursiveStruct">>}>
46+
struct RecursiveStruct {
47+
int value;
48+
struct RecursiveStruct *next;
49+
};
50+
// CHECK3: testRecursiveStruct(%arg0: !cir.ptr<![[STRUCT]]>
51+
void testRecursiveStruct(struct RecursiveStruct *arg) {
52+
// CHECK3: %[[#NEXT:]] = cir.get_member %{{.+}}[1] {name = "next"} : !cir.ptr<![[STRUCT]]> -> !cir.ptr<!cir.ptr<![[STRUCT]]>>
53+
// CHECK3: %[[#DEREF:]] = cir.load{{.*}} %[[#NEXT]] : !cir.ptr<!cir.ptr<![[STRUCT]]>>, !cir.ptr<![[STRUCT]]>
54+
// CHECK3: cir.get_member %[[#DEREF]][0] {name = "value"} : !cir.ptr<![[STRUCT]]> -> !cir.ptr<!s32i>
55+
arg->next->value;
56+
}
57+
58+
59+
60+
//--- indirect_recursive_struct
61+
62+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %t/indirect_recursive_struct -o %t/indirect_recursive_struct.cir
63+
// RUN: FileCheck --check-prefix=CHECK4 --input-file=%t/indirect_recursive_struct.cir %s
64+
65+
// Node B refers to A, and vice-versa, so a forward declaration is used to
66+
// ensure the classes can be defined. Since types alias are not yet supported
67+
// in recursive type, each struct is expanded until there are no more recursive
68+
// types, or all the recursive types are self references.
69+
70+
// CHECK4: ![[B:.+]] = !cir.record<struct "StructNodeB" {!s32i, !cir.ptr<!cir.record<struct "StructNodeA" {!s32i, !cir.ptr<!cir.record<struct "StructNodeB">>}
71+
// CHECK4: ![[A:.+]] = !cir.record<struct "StructNodeA" {!s32i, !cir.ptr<![[B]]>}>
72+
struct StructNodeB;
73+
struct StructNodeA {
74+
int value;
75+
struct StructNodeB *next;
76+
};
77+
struct StructNodeB {
78+
int value;
79+
struct StructNodeA *next;
80+
};
81+
82+
void testIndirectSelfReference(struct StructNodeA arg) {
83+
// CHECK4: %[[#V1:]] = cir.get_member %{{.+}}[1] {name = "next"} : !cir.ptr<![[A]]> -> !cir.ptr<!cir.ptr<![[B]]>>
84+
// CHECK4: %[[#V2:]] = cir.load{{.*}} %[[#V1]] : !cir.ptr<!cir.ptr<![[B]]>>, !cir.ptr<![[B]]>
85+
// CHECK4: %[[#V3:]] = cir.get_member %[[#V2]][1] {name = "next"} : !cir.ptr<![[B]]> -> !cir.ptr<!cir.ptr<![[A]]>>
86+
// CHECK4: %[[#V4:]] = cir.load{{.*}} %[[#V3]] : !cir.ptr<!cir.ptr<![[A]]>>, !cir.ptr<![[A]]>
87+
// CHECK4: cir.get_member %[[#V4]][0] {name = "value"} : !cir.ptr<![[A]]> -> !cir.ptr<!s32i>
88+
arg.next->next->value;
89+
}
90+
91+
92+
93+
//--- complex_struct
94+
95+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %t/complex_struct -o %t/complex_struct.cir
96+
// RUN: FileCheck --check-prefix=CHECK5 --input-file=%t/complex_struct.cir %s
97+
98+
// A sizeable complex struct just to double check that stuff is working.
99+
// CHECK5: !cir.record<struct "anon.0" {!cir.ptr<!cir.record<struct "A" {!cir.record<struct "anon.0">, !cir.record<struct "B" {!cir.ptr<!cir.record<struct "B">>, !cir.record<struct "C" {!cir.ptr<!cir.record<struct "A">>, !cir.ptr<!cir.record<struct "B">>, !cir.ptr<!cir.record<struct "C">>}>, !cir.record<union "anon.1" {!cir.ptr<!cir.record<struct "A">>, !cir.record<struct "anon.2" {!cir.ptr<!cir.record<struct "B">>}>}>}>}>>}>
100+
// CHECK5: !cir.record<struct "C" {!cir.ptr<!cir.record<struct "A" {!rec_anon2E0, !cir.record<struct "B" {!cir.ptr<!cir.record<struct "B">>, !cir.record<struct "C">, !cir.record<union "anon.1" {!cir.ptr<!cir.record<struct "A">>, !cir.record<struct "anon.2" {!cir.ptr<!cir.record<struct "B">>}>}>}>}>>, !cir.ptr<!cir.record<struct "B" {!cir.ptr<!cir.record<struct "B">>, !cir.record<struct "C">, !cir.record<union "anon.1" {!cir.ptr<!cir.record<struct "A" {!rec_anon2E0, !cir.record<struct "B">}>>, !cir.record<struct "anon.2" {!cir.ptr<!cir.record<struct "B">>}>}>}>>, !cir.ptr<!cir.record<struct "C">>}>
101+
// CHECK5: !cir.record<struct "anon.2" {!cir.ptr<!cir.record<struct "B" {!cir.ptr<!cir.record<struct "B">>, !rec_C, !cir.record<union "anon.1" {!cir.ptr<!cir.record<struct "A" {!rec_anon2E0, !cir.record<struct "B">}>>, !cir.record<struct "anon.2">}>}>>}>
102+
// CHECK5: !cir.record<union "anon.1" {!cir.ptr<!cir.record<struct "A" {!rec_anon2E0, !cir.record<struct "B" {!cir.ptr<!cir.record<struct "B">>, !rec_C, !cir.record<union "anon.1">}>}>>, !rec_anon2E2}>
103+
// CHECK5: !cir.record<struct "B" {!cir.ptr<!cir.record<struct "B">>, !rec_C, !rec_anon2E1}>
104+
// CHECK5: !cir.record<struct "A" {!rec_anon2E0, !rec_B}>
105+
struct A {
106+
struct {
107+
struct A *a1;
108+
};
109+
struct B {
110+
struct B *b1;
111+
struct C {
112+
struct A *a2;
113+
struct B *b2;
114+
struct C *c1;
115+
} c;
116+
union {
117+
struct A *a2;
118+
struct {
119+
struct B *b3;
120+
};
121+
} u;
122+
} b;
123+
};
124+
void test(struct A *a){};

clang/test/CIR/CodeGen/forward-enum.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck %s --input-file=%t.cir
3+
4+
extern enum X x;
5+
void f(void) {
6+
x;
7+
}
8+
9+
enum X {
10+
One,
11+
Two
12+
};
13+
14+
// CHECK: cir.global "private" external @x : !u32i
15+
// CHECK: cir.func{{.*}} @f
16+
// CHECK: cir.get_global @x : !cir.ptr<!u32i>

0 commit comments

Comments
 (0)