Skip to content

Commit 92294ed

Browse files
committed
[CIR] Upstream framework for NRVO cleanup
This upstreams the framework code to handle NRVO cleanup. At this point the implementation follows the incubator in not performing the actual cleanup, but it populates the EH stack so that the cleanup can be done. This inserts a flag variable that tracks whether we are performing an NRVO return. Classic codegen checks this variable and calls a destructor if it is not set during normal cleanup. The check and destructor call are not yet implemented here.
1 parent e52aece commit 92294ed

File tree

6 files changed

+117
-4
lines changed

6 files changed

+117
-4
lines changed

clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,11 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
331331
return cir::StoreOp::create(*this, loc, val, dst, isVolatile, align, order);
332332
}
333333

334+
cir::StoreOp createFlagStore(mlir::Location loc, bool val, mlir::Value dst) {
335+
mlir::Value flag = getBool(val, loc);
336+
return CIRBaseBuilderTy::createStore(loc, flag, dst);
337+
}
338+
334339
[[nodiscard]] cir::GlobalOp createGlobal(mlir::ModuleOp mlirModule,
335340
mlir::Location loc,
336341
mlir::StringRef name,

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ struct MissingFeatures {
227227
static bool countedBySize() { return false; }
228228
static bool cgFPOptionsRAII() { return false; }
229229
static bool checkBitfieldClipping() { return false; }
230+
static bool cleanupDestroyNRVOVariable() { return false; }
230231
static bool cirgenABIInfo() { return false; }
231232
static bool cleanupAfterErrorDiags() { return false; }
232233
static bool cleanupAppendInsts() { return false; }

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,22 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
9898
if (const RecordDecl *rd = ty->getAsRecordDecl()) {
9999
if (const auto *cxxrd = dyn_cast<CXXRecordDecl>(rd);
100100
(cxxrd && !cxxrd->hasTrivialDestructor()) ||
101-
rd->isNonTrivialToPrimitiveDestroy())
102-
cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: set NRVO flag");
101+
rd->isNonTrivialToPrimitiveDestroy()) {
102+
// In LLVM: Create a flag that is used to indicate when the NRVO was
103+
// applied to this variable. Set it to zero to indicate that NRVO was
104+
// not applied. For now, use the same approach for CIRGen until we can
105+
// be sure it's worth doing something more aggressive.
106+
cir::ConstantOp falseNVRO = builder.getFalse(loc);
107+
Address nrvoFlag = createTempAlloca(falseNVRO.getType(),
108+
CharUnits::One(), loc, "nrvo",
109+
/*ArraySize=*/nullptr, &address);
110+
assert(builder.getInsertionBlock());
111+
builder.createStore(loc, falseNVRO, nrvoFlag);
112+
113+
// Record the NRVO flag for this variable.
114+
nrvoFlags[&d] = nrvoFlag.getPointer();
115+
emission.nrvoFlag = nrvoFlag.getPointer();
116+
}
103117
}
104118
} else {
105119
// A normal fixed sized variable becomes an alloca in the entry block,
@@ -809,6 +823,35 @@ struct DestroyObject final : EHScopeStack::Cleanup {
809823
}
810824
};
811825

826+
template <class Derived> struct DestroyNRVOVariable : EHScopeStack::Cleanup {
827+
DestroyNRVOVariable(Address addr, QualType type, mlir::Value nrvoFlag)
828+
: nrvoFlag(nrvoFlag), addr(addr), ty(type) {}
829+
830+
mlir::Value nrvoFlag;
831+
Address addr;
832+
QualType ty;
833+
834+
void emit(CIRGenFunction &cgf) override {
835+
assert(!cir::MissingFeatures::cleanupDestroyNRVOVariable());
836+
}
837+
838+
virtual ~DestroyNRVOVariable() = default;
839+
};
840+
841+
struct DestroyNRVOVariableCXX final
842+
: DestroyNRVOVariable<DestroyNRVOVariableCXX> {
843+
DestroyNRVOVariableCXX(Address addr, QualType type,
844+
const CXXDestructorDecl *dtor, mlir::Value nrvoFlag)
845+
: DestroyNRVOVariable<DestroyNRVOVariableCXX>(addr, type, nrvoFlag),
846+
dtor(dtor) {}
847+
848+
const CXXDestructorDecl *dtor;
849+
850+
void emitDestructorCall(CIRGenFunction &cgf) {
851+
assert(!cir::MissingFeatures::cleanupDestroyNRVOVariable());
852+
}
853+
};
854+
812855
struct CallStackRestore final : EHScopeStack::Cleanup {
813856
Address stack;
814857
CallStackRestore(Address stack) : stack(stack) {}
@@ -965,7 +1008,10 @@ void CIRGenFunction::emitAutoVarTypeCleanup(
9651008
// If there's an NRVO flag on the emission, we need a different
9661009
// cleanup.
9671010
if (emission.nrvoFlag) {
968-
cgm.errorNYI(var->getSourceRange(), "emitAutoVarTypeCleanup: NRVO");
1011+
assert(!type->isArrayType());
1012+
CXXDestructorDecl *dtor = type->getAsCXXRecordDecl()->getDestructor();
1013+
ehStack.pushCleanup<DestroyNRVOVariableCXX>(cleanupKind, addr, type, dtor,
1014+
emission.nrvoFlag);
9691015
return;
9701016
}
9711017
// Otherwise, this is handled below.

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ class CIRGenFunction : public CIRGenTypeCache {
123123

124124
GlobalDecl curSEHParent;
125125

126+
/// A mapping from NRVO variables to the flags used to indicate
127+
/// when the NRVO has been applied to this variable.
128+
llvm::DenseMap<const VarDecl *, mlir::Value> nrvoFlags;
129+
126130
llvm::DenseMap<const clang::ValueDecl *, clang::FieldDecl *>
127131
lambdaCaptureFields;
128132
clang::FieldDecl *lambdaThisCaptureField = nullptr;

clang/lib/CIR/CodeGen/CIRGenStmt.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,14 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
458458
if (getContext().getLangOpts().ElideConstructors && s.getNRVOCandidate() &&
459459
s.getNRVOCandidate()->isNRVOVariable()) {
460460
assert(!cir::MissingFeatures::openMP());
461-
assert(!cir::MissingFeatures::nrvo());
461+
// Apply the named return value optimization for this return statement,
462+
// which means doing nothing: the appropriate result has already been
463+
// constructed into the NRVO variable.
464+
465+
// If there is an NRVO flag for this variable, set it to 1 into indicate
466+
// that the cleanup code should not destroy the variable.
467+
if (auto nrvoFlag = nrvoFlags[s.getNRVOCandidate()])
468+
builder.createFlagStore(loc, true, nrvoFlag);
462469
} else if (!rv) {
463470
// No return expression. Do nothing.
464471
} else if (rv->getType()->isVoidType()) {

clang/test/CIR/CodeGen/nrvo.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,53 @@ struct S f1() {
4949
// OGCG-NEXT: call void @_ZN1SC1Ev(ptr {{.*}} %[[RETVAL]])
5050
// OGCG-NEXT: %[[RET:.*]] = load i64, ptr %[[RETVAL]]
5151
// OGCG-NEXT: ret i64 %[[RET]]
52+
53+
struct NonTrivial {
54+
~NonTrivial();
55+
};
56+
57+
void maybeThrow();
58+
59+
NonTrivial test_nrvo() {
60+
NonTrivial result;
61+
maybeThrow();
62+
return result;
63+
}
64+
65+
// TODO(cir): Handle normal cleanup properly.
66+
67+
// CIR: cir.func {{.*}} @_Z9test_nrvov()
68+
// CIR: %[[RESULT:.*]] = cir.alloca !rec_NonTrivial, !cir.ptr<!rec_NonTrivial>, ["__retval"]
69+
// CIR: %[[NRVO_FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["nrvo"]
70+
// CIR: %[[FALSE:.*]] = cir.const #false
71+
// CIR: cir.store{{.*}} %[[FALSE]], %[[NRVO_FLAG]]
72+
// CIR: cir.call @_Z10maybeThrowv() : () -> ()
73+
// CIR: %[[TRUE:.*]] = cir.const #true
74+
// CIR: cir.store{{.*}} %[[TRUE]], %[[NRVO_FLAG]]
75+
// CIR: %[[RET:.*]] = cir.load %[[RESULT]]
76+
// CIR: cir.return %[[RET]]
77+
78+
// LLVM: define {{.*}} %struct.NonTrivial @_Z9test_nrvov()
79+
// LLVM: %[[RESULT:.*]] = alloca %struct.NonTrivial
80+
// LLVM: %[[NRVO_FLAG:.*]] = alloca i8
81+
// LLVM: store i8 0, ptr %[[NRVO_FLAG]]
82+
// LLVM: call void @_Z10maybeThrowv()
83+
// LLVM: store i8 1, ptr %[[NRVO_FLAG]]
84+
// LLVM: %[[RET:.*]] = load %struct.NonTrivial, ptr %[[RESULT]]
85+
// LLVM: ret %struct.NonTrivial %[[RET]]
86+
87+
// OGCG: define {{.*}} void @_Z9test_nrvov(ptr {{.*}} sret(%struct.NonTrivial) {{.*}} %[[RESULT:.*]])
88+
// OGCG: %[[RESULT_ADDR:.*]] = alloca ptr
89+
// OGCG: %[[NRVO_FLAG:.*]] = alloca i1, align 1
90+
// OGCG: store ptr %[[RESULT]], ptr %[[RESULT_ADDR]]
91+
// OGCG: store i1 false, ptr %[[NRVO_FLAG]]
92+
// OGCG: call void @_Z10maybeThrowv()
93+
// OGCG: store i1 true, ptr %[[NRVO_FLAG]]
94+
// OGCG: %[[NRVO_VAL:.*]] = load i1, ptr %[[NRVO_FLAG]]
95+
// OGCG: br i1 %[[NRVO_VAL]], label %[[SKIPDTOR:.*]], label %[[NRVO_UNUSED:.*]]
96+
// OGCG: [[NRVO_UNUSED]]:
97+
// OGCG: call void @_ZN10NonTrivialD1Ev(ptr {{.*}} %[[RESULT]])
98+
// OGCG: br label %[[SKIPDTOR]]
99+
// OGCG: [[SKIPDTOR]]:
100+
// OGCG: ret void
101+

0 commit comments

Comments
 (0)