Skip to content

Commit 6432e94

Browse files
andykaylorgithub-actions[bot]
authored andcommitted
Automerge: [CIR] Add support for copy elision (#157713)
This adds basic support for eliding copy constructors. In order to make this possible, it also adds support for returning structures. This support does not include setting an NRVO flag when the class whose copy is being elided has a non-trivial destructor.
2 parents f1584a2 + aa9af2a commit 6432e94

File tree

7 files changed

+117
-13
lines changed

7 files changed

+117
-13
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ struct MissingFeatures {
244244
static bool moduleNameHash() { return false; }
245245
static bool msabi() { return false; }
246246
static bool needsGlobalCtorDtor() { return false; }
247+
static bool nrvo() { return false; }
247248
static bool objCBlocks() { return false; }
248249
static bool objCGC() { return false; }
249250
static bool objCLifetime() { return false; }

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
3131
cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: address space");
3232

3333
mlir::Location loc = getLoc(d.getSourceRange());
34+
bool nrvo =
35+
getContext().getLangOpts().ElideConstructors && d.isNRVOVariable();
3436

3537
CIRGenFunction::AutoVarEmission emission(d);
3638
emission.IsEscapingByRef = d.isEscapingByref();
@@ -44,16 +46,37 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
4446
if (ty->isVariablyModifiedType())
4547
cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: variably modified type");
4648

49+
assert(!cir::MissingFeatures::openMP());
50+
4751
Address address = Address::invalid();
4852
if (!ty->isConstantSizeType())
4953
cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: non-constant size type");
5054

5155
// A normal fixed sized variable becomes an alloca in the entry block,
52-
mlir::Type allocaTy = convertTypeForMem(ty);
53-
// Create the temp alloca and declare variable using it.
54-
address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
55-
/*arraySize=*/nullptr, /*alloca=*/nullptr, ip);
56-
declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()), alignment);
56+
// unless:
57+
// - it's an NRVO variable.
58+
// - we are compiling OpenMP and it's an OpenMP local variable.
59+
if (nrvo) {
60+
// The named return value optimization: allocate this variable in the
61+
// return slot, so that we can elide the copy when returning this
62+
// variable (C++0x [class.copy]p34).
63+
address = returnValue;
64+
65+
if (const RecordDecl *rd = ty->getAsRecordDecl()) {
66+
if (const auto *cxxrd = dyn_cast<CXXRecordDecl>(rd);
67+
(cxxrd && !cxxrd->hasTrivialDestructor()) ||
68+
rd->isNonTrivialToPrimitiveDestroy())
69+
cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: set NRVO flag");
70+
}
71+
} else {
72+
// A normal fixed sized variable becomes an alloca in the entry block,
73+
mlir::Type allocaTy = convertTypeForMem(ty);
74+
// Create the temp alloca and declare variable using it.
75+
address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
76+
/*arraySize=*/nullptr, /*alloca=*/nullptr, ip);
77+
declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()),
78+
alignment);
79+
}
5780

5881
emission.Addr = address;
5982
setAddrOfLocalVar(&d, address);

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,8 +1986,16 @@ void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
19861986

19871987
// Elide the constructor if we're constructing from a temporary
19881988
if (getLangOpts().ElideConstructors && e->isElidable()) {
1989-
cgm.errorNYI(e->getSourceRange(),
1990-
"emitCXXConstructExpr: elidable constructor");
1989+
// FIXME: This only handles the simplest case, where the source object is
1990+
// passed directly as the first argument to the constructor. This
1991+
// should also handle stepping through implicit casts and conversion
1992+
// sequences which involve two steps, with a conversion operator
1993+
// follwed by a converting constructor.
1994+
const Expr *srcObj = e->getArg(0);
1995+
assert(srcObj->isTemporaryObject(getContext(), cd->getParent()));
1996+
assert(
1997+
getContext().hasSameUnqualifiedType(e->getType(), srcObj->getType()));
1998+
emitAggExpr(srcObj, dest);
19911999
return;
19922000
}
19932001

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,10 @@ bool CIRGenFunction::constantFoldsToSimpleInteger(const Expr *cond,
206206
void CIRGenFunction::emitAndUpdateRetAlloca(QualType type, mlir::Location loc,
207207
CharUnits alignment) {
208208
if (!type->isVoidType()) {
209-
fnRetAlloca = emitAlloca("__retval", convertType(type), loc, alignment,
210-
/*insertIntoFnEntryBlock=*/false);
209+
mlir::Value addr = emitAlloca("__retval", convertType(type), loc, alignment,
210+
/*insertIntoFnEntryBlock=*/false);
211+
fnRetAlloca = addr;
212+
returnValue = Address(addr, alignment);
211213
}
212214
}
213215

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ class CIRGenFunction : public CIRGenTypeCache {
6666
/// The compiler-generated variable that holds the return value.
6767
std::optional<mlir::Value> fnRetAlloca;
6868

69+
/// The temporary alloca to hold the return value. This is
70+
/// invalid iff the function has no return value.
71+
Address returnValue = Address::invalid();
72+
6973
/// Tracks function scope overall cleanup handling.
7074
EHScopeStack ehStack;
7175

@@ -726,6 +730,14 @@ class CIRGenFunction : public CIRGenTypeCache {
726730
const CXXRecordDecl *base,
727731
bool baseIsVirtual);
728732

733+
/// Determine whether a return value slot may overlap some other object.
734+
AggValueSlot::Overlap_t getOverlapForReturnValue() {
735+
// FIXME: Assuming no overlap here breaks guaranteed copy elision for base
736+
// class subobjects. These cases may need to be revisited depending on the
737+
// resolution of the relevant core issue.
738+
return AggValueSlot::DoesNotOverlap;
739+
}
740+
729741
/// Determine whether a base class initialization may overlap some other
730742
/// object.
731743
AggValueSlot::Overlap_t getOverlapForBaseInit(const CXXRecordDecl *rd,

clang/lib/CIR/CodeGen/CIRGenStmt.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,8 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
445445

446446
if (getContext().getLangOpts().ElideConstructors && s.getNRVOCandidate() &&
447447
s.getNRVOCandidate()->isNRVOVariable()) {
448-
getCIRGenModule().errorNYI(s.getSourceRange(),
449-
"named return value optimization");
448+
assert(!cir::MissingFeatures::openMP());
449+
assert(!cir::MissingFeatures::nrvo());
450450
} else if (!rv) {
451451
// No return expression. Do nothing.
452452
} else if (rv->getType()->isVoidType()) {
@@ -471,9 +471,16 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
471471
builder.CIRBaseBuilderTy::createStore(loc, value, *fnRetAlloca);
472472
}
473473
break;
474-
default:
474+
case cir::TEK_Complex:
475475
getCIRGenModule().errorNYI(s.getSourceRange(),
476-
"non-scalar function return type");
476+
"complex function return type");
477+
break;
478+
case cir::TEK_Aggregate:
479+
assert(!cir::MissingFeatures::aggValueSlotGC());
480+
emitAggExpr(rv, AggValueSlot::forAddr(returnValue, Qualifiers(),
481+
AggValueSlot::IsDestructed,
482+
AggValueSlot::IsNotAliased,
483+
getOverlapForReturnValue()));
477484
break;
478485
}
479486
}

clang/test/CIR/CodeGen/nrvo.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-elide-constructors -fclangir -emit-cir %s -o %t-noelide.cir
4+
// RUN: FileCheck --input-file=%t-noelide.cir %s --check-prefix=CIR-NOELIDE
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
6+
// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
7+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
8+
// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG
9+
10+
// There are no LLVM and OGCG tests with -fno-elide-constructors because the
11+
// lowering isn't of interest for this test. We just need to see that the
12+
// copy constructor is elided without -fno-elide-constructors but not with it.
13+
14+
struct S {
15+
S();
16+
int a;
17+
int b;
18+
};
19+
20+
struct S f1() {
21+
S s;
22+
return s;
23+
}
24+
25+
// CIR: cir.func{{.*}} @_Z2f1v() -> !rec_S {
26+
// CIR-NEXT: %[[RETVAL:.*]] = cir.alloca !rec_S, !cir.ptr<!rec_S>, ["__retval", init]
27+
// CIR-NEXT: cir.call @_ZN1SC1Ev(%[[RETVAL]]) : (!cir.ptr<!rec_S>) -> ()
28+
// CIR-NEXT: %[[RET:.*]] = cir.load %[[RETVAL]] : !cir.ptr<!rec_S>, !rec_S
29+
// CIR-NEXT: cir.return %[[RET]]
30+
31+
// CIR-NOELIDE: cir.func{{.*}} @_Z2f1v() -> !rec_S {
32+
// CIR-NOELIDE-NEXT: %[[RETVAL:.*]] = cir.alloca !rec_S, !cir.ptr<!rec_S>, ["__retval"]
33+
// CIR-NOELIDE-NEXT: %[[S:.*]] = cir.alloca !rec_S, !cir.ptr<!rec_S>, ["s", init]
34+
// CIR-NOELIDE-NEXT: cir.call @_ZN1SC1Ev(%[[S]]) : (!cir.ptr<!rec_S>) -> ()
35+
// CIR-NOELIDE-NEXT: cir.call @_ZN1SC1EOS_(%[[RETVAL]], %[[S]]){{.*}} : (!cir.ptr<!rec_S>, !cir.ptr<!rec_S>) -> ()
36+
// CIR-NOELIDE-NEXT: %[[RET:.*]] = cir.load %[[RETVAL]] : !cir.ptr<!rec_S>, !rec_S
37+
// CIR-NOELIDE-NEXT: cir.return %[[RET]]
38+
39+
// FIXME: Update this when calling convetnion lowering is implemented.
40+
// LLVM: define{{.*}} %struct.S @_Z2f1v()
41+
// LLVM-NEXT: %[[RETVAL:.*]] = alloca %struct.S
42+
// LLVM-NEXT: call void @_ZN1SC1Ev(ptr %[[RETVAL]])
43+
// LLVM-NEXT: %[[RET:.*]] = load %struct.S, ptr %[[RETVAL]]
44+
// LLVM-NEXT: ret %struct.S %[[RET]]
45+
46+
// OGCG: define{{.*}} i64 @_Z2f1v()
47+
// OGCG-NEXT: entry:
48+
// OGCG-NEXT: %[[RETVAL:.*]] = alloca %struct.S
49+
// OGCG-NEXT: call void @_ZN1SC1Ev(ptr {{.*}} %[[RETVAL]])
50+
// OGCG-NEXT: %[[RET:.*]] = load i64, ptr %[[RETVAL]]
51+
// OGCG-NEXT: ret i64 %[[RET]]

0 commit comments

Comments
 (0)