Skip to content

Commit 8d9f516

Browse files
authored
[CIR] Unblock simple C++ structure support (#138368)
This change adds additional checks to a few places where a simple struct in C++ code was triggering `errorNYI` in places where no additional handling was needed, and adds a very small amount of trivial initialization. The code now checks for the conditions that do require extra handling before issuing the diagnostic. New tests are added for declaring and using a simple struct in C++ code.
1 parent 55a88cd commit 8d9f516

File tree

9 files changed

+222
-23
lines changed

9 files changed

+222
-23
lines changed

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,12 @@ LValue CIRGenFunction::emitLValueForField(LValue base, const FieldDecl *field) {
322322
assert(!cir::MissingFeatures::opTBAA());
323323

324324
Address addr = base.getAddress();
325-
if (isa<CXXRecordDecl>(rec)) {
326-
cgm.errorNYI(field->getSourceRange(), "emitLValueForField: C++ class");
327-
return LValue();
325+
if (auto *classDecl = dyn_cast<CXXRecordDecl>(rec)) {
326+
if (cgm.getCodeGenOpts().StrictVTablePointers &&
327+
classDecl->isDynamicClass()) {
328+
cgm.errorNYI(field->getSourceRange(),
329+
"emitLValueForField: strict vtable for dynamic class");
330+
}
328331
}
329332

330333
unsigned recordCVR = base.getVRQualifiers();

clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "CIRGenConstantEmitter.h"
1515
#include "CIRGenFunction.h"
1616
#include "CIRGenModule.h"
17+
#include "CIRGenRecordLayout.h"
1718
#include "mlir/IR/Attributes.h"
1819
#include "mlir/IR/BuiltinAttributeInterfaces.h"
1920
#include "mlir/IR/BuiltinAttributes.h"
@@ -365,12 +366,33 @@ mlir::Attribute ConstantEmitter::tryEmitPrivateForVarInit(const VarDecl &d) {
365366
// initialization of memory to all NULLs.
366367
if (!d.hasLocalStorage()) {
367368
QualType ty = cgm.getASTContext().getBaseElementType(d.getType());
368-
if (ty->isRecordType())
369-
if (d.getInit() && isa<CXXConstructExpr>(d.getInit())) {
370-
cgm.errorNYI(d.getInit()->getBeginLoc(),
371-
"tryEmitPrivateForVarInit CXXConstructExpr");
372-
return {};
369+
if (ty->isRecordType()) {
370+
if (const auto *e = dyn_cast_or_null<CXXConstructExpr>(d.getInit())) {
371+
const CXXConstructorDecl *cd = e->getConstructor();
372+
// FIXME: we should probably model this more closely to C++ than
373+
// just emitting a global with zero init (mimic what we do for trivial
374+
// assignments and whatnots). Since this is for globals shouldn't
375+
// be a problem for the near future.
376+
if (cd->isTrivial() && cd->isDefaultConstructor()) {
377+
const auto *cxxrd =
378+
cast<CXXRecordDecl>(ty->getAs<RecordType>()->getDecl());
379+
if (cxxrd->getNumBases() != 0) {
380+
// There may not be anything additional to do here, but this will
381+
// force us to pause and test this path when it is supported.
382+
cgm.errorNYI("tryEmitPrivateForVarInit: cxx record with bases");
383+
return {};
384+
}
385+
if (!cgm.getTypes().isZeroInitializable(cxxrd)) {
386+
// To handle this case, we really need to go through
387+
// emitNullConstant, but we need an attribute, not a value
388+
cgm.errorNYI(
389+
"tryEmitPrivateForVarInit: non-zero-initializable cxx record");
390+
return {};
391+
}
392+
return cir::ZeroAttr::get(cgm.convertType(d.getType()));
393+
}
373394
}
395+
}
374396
}
375397
inConstantContext = d.hasConstantInitialization();
376398

clang/lib/CIR/CodeGen/CIRGenRecordLayout.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,23 @@ class CIRGenRecordLayout {
3333
/// field no. This info is populated by the record builder.
3434
llvm::DenseMap<const clang::FieldDecl *, unsigned> fieldIdxMap;
3535

36+
/// False if any direct or indirect subobject of this class, when considered
37+
/// as a complete object, requires a non-zero bitpattern when
38+
/// zero-initialized.
39+
LLVM_PREFERRED_TYPE(bool)
40+
unsigned zeroInitializable : 1;
41+
42+
/// False if any direct or indirect subobject of this class, when considered
43+
/// as a base subobject, requires a non-zero bitpattern when zero-initialized.
44+
LLVM_PREFERRED_TYPE(bool)
45+
unsigned zeroInitializableAsBase : 1;
46+
3647
public:
37-
CIRGenRecordLayout(cir::RecordType completeObjectType)
38-
: completeObjectType(completeObjectType) {}
48+
CIRGenRecordLayout(cir::RecordType completeObjectType, bool zeroInitializable,
49+
bool zeroInitializableAsBase)
50+
: completeObjectType(completeObjectType),
51+
zeroInitializable(zeroInitializable),
52+
zeroInitializableAsBase(zeroInitializableAsBase) {}
3953

4054
/// Return the "complete object" LLVM type associated with
4155
/// this record.
@@ -47,6 +61,14 @@ class CIRGenRecordLayout {
4761
assert(fieldIdxMap.count(fd) && "Invalid field for record!");
4862
return fieldIdxMap.lookup(fd);
4963
}
64+
65+
/// Check whether this struct can be C++ zero-initialized
66+
/// with a zeroinitializer.
67+
bool isZeroInitializable() const { return zeroInitializable; }
68+
69+
/// Check whether this struct can be C++ zero-initialized
70+
/// with a zeroinitializer when considered as a base subobject.
71+
bool isZeroInitializableAsBase() const { return zeroInitializableAsBase; }
5072
};
5173

5274
} // namespace clang::CIRGen

clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ struct CIRRecordLowering final {
7777
return astContext.toCharUnitsFromBits(bitOffset);
7878
}
7979

80+
void calculateZeroInit();
81+
8082
CharUnits getSize(mlir::Type Ty) {
8183
return CharUnits::fromQuantity(dataLayout.layout.getTypeSize(Ty));
8284
}
@@ -177,18 +179,26 @@ void CIRRecordLowering::lower() {
177179
return;
178180
}
179181

180-
if (isa<CXXRecordDecl>(recordDecl)) {
181-
cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
182-
"lower: class");
183-
return;
184-
}
185-
186182
assert(!cir::MissingFeatures::cxxSupport());
187183

188184
CharUnits size = astRecordLayout.getSize();
189185

190186
accumulateFields();
191187

188+
if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl)) {
189+
if (cxxRecordDecl->getNumBases() > 0) {
190+
CIRGenModule &cgm = cirGenTypes.getCGModule();
191+
cgm.errorNYI(recordDecl->getSourceRange(),
192+
"CIRRecordLowering::lower: derived CXXRecordDecl");
193+
return;
194+
}
195+
if (members.empty()) {
196+
appendPaddingBytes(size);
197+
assert(!cir::MissingFeatures::bitfields());
198+
return;
199+
}
200+
}
201+
192202
llvm::stable_sort(members);
193203
// TODO: implement clipTailPadding once bitfields are implemented
194204
assert(!cir::MissingFeatures::bitfields());
@@ -199,6 +209,7 @@ void CIRRecordLowering::lower() {
199209
insertPadding();
200210
members.pop_back();
201211

212+
calculateZeroInit();
202213
fillOutputFields();
203214
}
204215

@@ -236,6 +247,19 @@ void CIRRecordLowering::accumulateFields() {
236247
}
237248
}
238249

250+
void CIRRecordLowering::calculateZeroInit() {
251+
for (const MemberInfo &member : members) {
252+
if (member.kind == MemberInfo::InfoKind::Field) {
253+
if (!member.fieldDecl || isZeroInitializable(member.fieldDecl))
254+
continue;
255+
zeroInitializable = zeroInitializableAsBase = false;
256+
return;
257+
}
258+
// TODO(cir): handle base types
259+
assert(!cir::MissingFeatures::cxxSupport());
260+
}
261+
}
262+
239263
void CIRRecordLowering::determinePacked() {
240264
if (packed)
241265
return;
@@ -295,7 +319,10 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
295319
// If we're in C++, compute the base subobject type.
296320
if (llvm::isa<CXXRecordDecl>(rd) && !rd->isUnion() &&
297321
!rd->hasAttr<FinalAttr>()) {
298-
cgm.errorNYI(rd->getSourceRange(), "computeRecordLayout: CXXRecordDecl");
322+
if (lowering.astRecordLayout.getNonVirtualSize() !=
323+
lowering.astRecordLayout.getSize()) {
324+
cgm.errorNYI(rd->getSourceRange(), "computeRecordLayout: CXXRecordDecl");
325+
}
299326
}
300327

301328
// Fill in the record *after* computing the base type. Filling in the body
@@ -304,7 +331,9 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
304331
assert(!cir::MissingFeatures::astRecordDeclAttr());
305332
ty->complete(lowering.fieldTypes, lowering.packed, lowering.padded);
306333

307-
auto rl = std::make_unique<CIRGenRecordLayout>(ty ? *ty : cir::RecordType());
334+
auto rl = std::make_unique<CIRGenRecordLayout>(
335+
ty ? *ty : cir::RecordType(), (bool)lowering.zeroInitializable,
336+
(bool)lowering.zeroInitializableAsBase);
308337

309338
assert(!cir::MissingFeatures::recordZeroInit());
310339
assert(!cir::MissingFeatures::cxxSupport());

clang/lib/CIR/CodeGen/CIRGenTypes.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,11 @@ mlir::Type CIRGenTypes::convertRecordDeclType(const clang::RecordDecl *rd) {
237237
assert(insertResult && "isSafeToCovert() should have caught this.");
238238

239239
// Force conversion of non-virtual base classes recursively.
240-
if (isa<CXXRecordDecl>(rd)) {
241-
cgm.errorNYI(rd->getSourceRange(), "CXXRecordDecl");
240+
if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(rd)) {
241+
if (cxxRecordDecl->getNumBases() > 0) {
242+
cgm.errorNYI(rd->getSourceRange(),
243+
"convertRecordDeclType: derived CXXRecordDecl");
244+
}
242245
}
243246

244247
// Layout fields.
@@ -497,9 +500,9 @@ bool CIRGenTypes::isZeroInitializable(clang::QualType t) {
497500
return true;
498501
}
499502

500-
if (t->getAs<RecordType>()) {
501-
cgm.errorNYI(SourceLocation(), "isZeroInitializable for RecordType", t);
502-
return false;
503+
if (const RecordType *rt = t->getAs<RecordType>()) {
504+
const RecordDecl *rd = rt->getDecl();
505+
return isZeroInitializable(rd);
503506
}
504507

505508
if (t->getAs<MemberPointerType>()) {
@@ -511,6 +514,10 @@ bool CIRGenTypes::isZeroInitializable(clang::QualType t) {
511514
return true;
512515
}
513516

517+
bool CIRGenTypes::isZeroInitializable(const RecordDecl *rd) {
518+
return getCIRGenRecordLayout(rd).isZeroInitializable();
519+
}
520+
514521
const CIRGenFunctionInfo &CIRGenTypes::arrangeCIRFunctionInfo(
515522
CanQualType returnType, llvm::ArrayRef<clang::CanQualType> argTypes) {
516523
assert(llvm::all_of(argTypes,

clang/lib/CIR/CodeGen/CIRGenTypes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ class CIRGenTypes {
120120
/// Return whether a type can be zero-initialized (in the C++ sense) with an
121121
/// LLVM zeroinitializer.
122122
bool isZeroInitializable(clang::QualType ty);
123+
bool isZeroInitializable(const RecordDecl *rd);
123124

124125
const CIRGenFunctionInfo &arrangeFreeFunctionCall(const CallArgList &args,
125126
const FunctionType *fnType);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - 2>&1 | FileCheck %s
2+
3+
struct Other {
4+
int x;
5+
};
6+
7+
struct Trivial {
8+
int x;
9+
double y;
10+
decltype(&Other::x) ptr;
11+
};
12+
13+
// This case has a trivial default constructor, but can't be zero-initialized.
14+
Trivial t;
15+
16+
// Since the case above isn't handled yet, we want a test that verifies that
17+
// we're failing for the right reason.
18+
19+
// CHECK: error: ClangIR code gen Not Yet Implemented: tryEmitPrivateForVarInit: non-zero-initializable cxx record

clang/test/CIR/CodeGen/struct.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ IncompleteS *p;
1212
// LLVM: @p = dso_local global ptr null
1313
// OGCG: @p = global ptr null, align 8
1414

15+
struct CompleteS {
16+
int a;
17+
char b;
18+
};
19+
20+
CompleteS cs;
21+
22+
// CIR: cir.global external @cs = #cir.zero : !rec_CompleteS
23+
// LLVM-DAG: @cs = dso_local global %struct.CompleteS zeroinitializer
24+
// OGCG-DAG: @cs = global %struct.CompleteS zeroinitializer, align 4
25+
1526
void f(void) {
1627
IncompleteS *p;
1728
}
@@ -28,3 +39,29 @@ void f(void) {
2839
// OGCG-NEXT: entry:
2940
// OGCG-NEXT: %[[P:.*]] = alloca ptr, align 8
3041
// OGCG-NEXT: ret void
42+
43+
char f2(CompleteS &s) {
44+
return s.b;
45+
}
46+
47+
// CIR: cir.func @_Z2f2R9CompleteS(%[[ARG_S:.*]]: !cir.ptr<!rec_CompleteS>{{.*}})
48+
// CIR: %[[S_ADDR:.*]] = cir.alloca !cir.ptr<!rec_CompleteS>, !cir.ptr<!cir.ptr<!rec_CompleteS>>, ["s", init, const]
49+
// CIR: cir.store %[[ARG_S]], %[[S_ADDR]]
50+
// CIR: %[[S_REF:.*]] = cir.load %[[S_ADDR]]
51+
// CIR: %[[S_ADDR2:.*]] = cir.get_member %[[S_REF]][1] {name = "b"}
52+
// CIR: %[[S_B:.*]] = cir.load %[[S_ADDR2]]
53+
54+
// LLVM: define i8 @_Z2f2R9CompleteS(ptr %[[ARG_S:.*]])
55+
// LLVM: %[[S_ADDR:.*]] = alloca ptr
56+
// LLVM: store ptr %[[ARG_S]], ptr %[[S_ADDR]]
57+
// LLVM: %[[S_REF:.*]] = load ptr, ptr %[[S_ADDR]], align 8
58+
// LLVM: %[[S_ADDR2:.*]] = getelementptr %struct.CompleteS, ptr %[[S_REF]], i32 0, i32 1
59+
// LLVM: %[[S_B:.*]] = load i8, ptr %[[S_ADDR2]]
60+
61+
// OGCG: define{{.*}} i8 @_Z2f2R9CompleteS(ptr{{.*}} %[[ARG_S:.*]])
62+
// OGCG: entry:
63+
// OGCG: %[[S_ADDR:.*]] = alloca ptr
64+
// OGCG: store ptr %[[ARG_S]], ptr %[[S_ADDR]]
65+
// OGCG: %[[S_REF:.*]] = load ptr, ptr %[[S_ADDR]]
66+
// OGCG: %[[S_ADDR2:.*]] = getelementptr inbounds nuw %struct.CompleteS, ptr %[[S_REF]], i32 0, i32 1
67+
// OGCG: %[[S_B:.*]] = load i8, ptr %[[S_ADDR2]]

clang/test/CIR/CodeGen/union.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
6+
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
7+
8+
// Should generate a union type with all members preserved.
9+
union U {
10+
bool b;
11+
short s;
12+
int i;
13+
float f;
14+
double d;
15+
};
16+
// CIR: !rec_U = !cir.record<union "U" {!cir.bool, !s16i, !s32i, !cir.float, !cir.double}>
17+
// LLVM: %union.U = type { double }
18+
// OGCG: %union.U = type { double }
19+
20+
void shouldGenerateUnionAccess(union U u) {
21+
u.b = true;
22+
u.b;
23+
u.i = 1;
24+
u.i;
25+
u.f = 0.1F;
26+
u.f;
27+
u.d = 0.1;
28+
u.d;
29+
}
30+
// CIR: cir.func {{.*}}shouldGenerateUnionAccess
31+
// CIR: %[[#BASE:]] = cir.get_member %0[0] {name = "b"} : !cir.ptr<!rec_U> -> !cir.ptr<!cir.bool>
32+
// CIR: cir.store %{{.+}}, %[[#BASE]] : !cir.bool, !cir.ptr<!cir.bool>
33+
// CIR: cir.get_member %0[0] {name = "b"} : !cir.ptr<!rec_U> -> !cir.ptr<!cir.bool>
34+
// CIR: %[[#BASE:]] = cir.get_member %0[2] {name = "i"} : !cir.ptr<!rec_U> -> !cir.ptr<!s32i>
35+
// CIR: cir.store %{{.+}}, %[[#BASE]] : !s32i, !cir.ptr<!s32i>
36+
// CIR: %[[#BASE:]] = cir.get_member %0[2] {name = "i"} : !cir.ptr<!rec_U> -> !cir.ptr<!s32i>
37+
// CIR: %[[#BASE:]] = cir.get_member %0[3] {name = "f"} : !cir.ptr<!rec_U> -> !cir.ptr<!cir.float>
38+
// CIR: cir.store %{{.+}}, %[[#BASE]] : !cir.float, !cir.ptr<!cir.float>
39+
// CIR: %[[#BASE:]] = cir.get_member %0[3] {name = "f"} : !cir.ptr<!rec_U> -> !cir.ptr<!cir.float>
40+
// CIR: %[[#BASE:]] = cir.get_member %0[4] {name = "d"} : !cir.ptr<!rec_U> -> !cir.ptr<!cir.double>
41+
// CIR: cir.store %{{.+}}, %[[#BASE]] : !cir.double, !cir.ptr<!cir.double>
42+
// CIR: %[[#BASE:]] = cir.get_member %0[4] {name = "d"} : !cir.ptr<!rec_U> -> !cir.ptr<!cir.double>
43+
44+
// LLVM: define {{.*}}shouldGenerateUnionAccess
45+
// LLVM: %[[BASE:.*]] = alloca %union.U
46+
// LLVM: store %union.U %{{.*}}, ptr %[[BASE]]
47+
// LLVM: store i8 1, ptr %[[BASE]]
48+
// LLVM: store i32 1, ptr %[[BASE]]
49+
// LLVM: store float 0x3FB99999A0000000, ptr %[[BASE]]
50+
// LLVM: store double 1.000000e-01, ptr %[[BASE]]
51+
52+
// OGCG: define {{.*}}shouldGenerateUnionAccess
53+
// OGCG: %[[BASE:.*]] = alloca %union.U
54+
// OGCG: %[[DIVE:.*]] = getelementptr inbounds nuw %union.U, ptr %[[BASE]], i32 0, i32 0
55+
// OGCG: store i64 %{{.*}}, ptr %[[DIVE]]
56+
// OGCG: store i8 1, ptr %[[BASE]]
57+
// OGCG: store i32 1, ptr %[[BASE]]
58+
// OGCG: store float 0x3FB99999A0000000, ptr %[[BASE]]
59+
// OGCG: store double 1.000000e-01, ptr %[[BASE]]

0 commit comments

Comments
 (0)