Skip to content

Commit 47b5917

Browse files
authored
[CIR] Add support for normal cleanups (#149948)
This change adds basic handling for normal cleanups. This is a very minimal implemention. In particular, it uses a naive substitute for the rich cleanup and EH stack handling that is present in classic codegen and the CIR incubator. This is intended as a temporary implementation to allow incremental progress. It is not expected to scale well enough to be used in a production environment. It will be replaced with the full EHScopeStack handling when such an implementation is needed.
1 parent f26c0d0 commit 47b5917

File tree

10 files changed

+554
-33
lines changed

10 files changed

+554
-33
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ struct MissingFeatures {
196196
static bool cxxRecordStaticMembers() { return false; }
197197
static bool dataLayoutTypeAllocSize() { return false; }
198198
static bool deferredCXXGlobalInit() { return false; }
199+
static bool ehCleanupFlags() { return false; }
200+
static bool ehstackBranches() { return false; }
199201
static bool emitCheckedInBoundsGEP() { return false; }
200202
static bool emitCondLikelihoodViaExpectIntrinsic() { return false; }
201203
static bool emitLifetimeMarkers() { return false; }

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,19 @@ void CIRGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &args) {
480480
s->getStmtClassName());
481481
}
482482

483+
void CIRGenFunction::destroyCXXObject(CIRGenFunction &cgf, Address addr,
484+
QualType type) {
485+
const RecordType *rtype = type->castAs<RecordType>();
486+
const CXXRecordDecl *record = cast<CXXRecordDecl>(rtype->getDecl());
487+
const CXXDestructorDecl *dtor = record->getDestructor();
488+
// TODO(cir): Unlike traditional codegen, CIRGen should actually emit trivial
489+
// dtors which shall be removed on later CIR passes. However, only remove this
490+
// assertion after we have a test case to exercise this path.
491+
assert(!dtor->isTrivial());
492+
cgf.emitCXXDestructorCall(dtor, Dtor_Complete, /*forVirtualBase*/ false,
493+
/*delegating=*/false, addr, type);
494+
}
495+
483496
void CIRGenFunction::emitDelegatingCXXConstructorCall(
484497
const CXXConstructorDecl *ctor, const FunctionArgList &args) {
485498
assert(ctor->isDelegatingConstructor());
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//===--- CIRGenCleanup.cpp - Bookkeeping and code emission for cleanups ---===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file contains code dealing with the IR generation for cleanups
10+
// and related information.
11+
//
12+
// A "cleanup" is a piece of code which needs to be executed whenever
13+
// control transfers out of a particular scope. This can be
14+
// conditionalized to occur only on exceptional control flow, only on
15+
// normal control flow, or both.
16+
//
17+
//===----------------------------------------------------------------------===//
18+
19+
#include "CIRGenFunction.h"
20+
21+
#include "clang/CIR/MissingFeatures.h"
22+
23+
using namespace clang;
24+
using namespace clang::CIRGen;
25+
26+
//===----------------------------------------------------------------------===//
27+
// CIRGenFunction cleanup related
28+
//===----------------------------------------------------------------------===//
29+
30+
//===----------------------------------------------------------------------===//
31+
// EHScopeStack
32+
//===----------------------------------------------------------------------===//
33+
34+
void EHScopeStack::Cleanup::anchor() {}
35+
36+
static mlir::Block *getCurCleanupBlock(CIRGenFunction &cgf) {
37+
mlir::OpBuilder::InsertionGuard guard(cgf.getBuilder());
38+
mlir::Block *cleanup =
39+
cgf.curLexScope->getOrCreateCleanupBlock(cgf.getBuilder());
40+
return cleanup;
41+
}
42+
43+
/// Pops a cleanup block. If the block includes a normal cleanup, the
44+
/// current insertion point is threaded through the cleanup, as are
45+
/// any branch fixups on the cleanup.
46+
void CIRGenFunction::popCleanupBlock() {
47+
assert(!ehStack.cleanupStack.empty() && "cleanup stack is empty!");
48+
mlir::OpBuilder::InsertionGuard guard(builder);
49+
std::unique_ptr<EHScopeStack::Cleanup> cleanup =
50+
ehStack.cleanupStack.pop_back_val();
51+
52+
assert(!cir::MissingFeatures::ehCleanupFlags());
53+
mlir::Block *cleanupEntry = getCurCleanupBlock(*this);
54+
builder.setInsertionPointToEnd(cleanupEntry);
55+
cleanup->emit(*this);
56+
}
57+
58+
/// Pops cleanup blocks until the given savepoint is reached.
59+
void CIRGenFunction::popCleanupBlocks(size_t oldCleanupStackDepth) {
60+
assert(!cir::MissingFeatures::ehstackBranches());
61+
62+
assert(ehStack.getStackDepth() >= oldCleanupStackDepth);
63+
64+
// Pop cleanup blocks until we reach the base stack depth for the
65+
// current scope.
66+
while (ehStack.getStackDepth() > oldCleanupStackDepth) {
67+
popCleanupBlock();
68+
}
69+
}

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ void CIRGenFunction::emitAutoVarCleanups(
183183
const VarDecl &d = *emission.Variable;
184184

185185
// Check the type for a cleanup.
186-
if (d.needsDestruction(getContext()))
187-
cgm.errorNYI(d.getSourceRange(), "emitAutoVarCleanups: type cleanup");
186+
if (QualType::DestructionKind dtorKind = d.needsDestruction(getContext()))
187+
emitAutoVarTypeCleanup(emission, dtorKind);
188188

189189
assert(!cir::MissingFeatures::opAllocaPreciseLifetime());
190190

@@ -648,3 +648,96 @@ void CIRGenFunction::emitNullabilityCheck(LValue lhs, mlir::Value rhs,
648648

649649
assert(!cir::MissingFeatures::sanitizers());
650650
}
651+
652+
/// Immediately perform the destruction of the given object.
653+
///
654+
/// \param addr - the address of the object; a type*
655+
/// \param type - the type of the object; if an array type, all
656+
/// objects are destroyed in reverse order
657+
/// \param destroyer - the function to call to destroy individual
658+
/// elements
659+
void CIRGenFunction::emitDestroy(Address addr, QualType type,
660+
Destroyer *destroyer) {
661+
if (getContext().getAsArrayType(type))
662+
cgm.errorNYI("emitDestroy: array type");
663+
664+
return destroyer(*this, addr, type);
665+
}
666+
667+
CIRGenFunction::Destroyer *
668+
CIRGenFunction::getDestroyer(QualType::DestructionKind kind) {
669+
switch (kind) {
670+
case QualType::DK_none:
671+
llvm_unreachable("no destroyer for trivial dtor");
672+
case QualType::DK_cxx_destructor:
673+
return destroyCXXObject;
674+
case QualType::DK_objc_strong_lifetime:
675+
case QualType::DK_objc_weak_lifetime:
676+
case QualType::DK_nontrivial_c_struct:
677+
cgm.errorNYI("getDestroyer: other destruction kind");
678+
return nullptr;
679+
}
680+
llvm_unreachable("Unknown DestructionKind");
681+
}
682+
683+
namespace {
684+
struct DestroyObject final : EHScopeStack::Cleanup {
685+
DestroyObject(Address addr, QualType type,
686+
CIRGenFunction::Destroyer *destroyer)
687+
: addr(addr), type(type), destroyer(destroyer) {}
688+
689+
Address addr;
690+
QualType type;
691+
CIRGenFunction::Destroyer *destroyer;
692+
693+
void emit(CIRGenFunction &cgf) override {
694+
cgf.emitDestroy(addr, type, destroyer);
695+
}
696+
};
697+
} // namespace
698+
699+
/// Enter a destroy cleanup for the given local variable.
700+
void CIRGenFunction::emitAutoVarTypeCleanup(
701+
const CIRGenFunction::AutoVarEmission &emission,
702+
QualType::DestructionKind dtorKind) {
703+
assert(dtorKind != QualType::DK_none);
704+
705+
// Note that for __block variables, we want to destroy the
706+
// original stack object, not the possibly forwarded object.
707+
Address addr = emission.getObjectAddress(*this);
708+
709+
const VarDecl *var = emission.Variable;
710+
QualType type = var->getType();
711+
712+
CleanupKind cleanupKind = NormalAndEHCleanup;
713+
CIRGenFunction::Destroyer *destroyer = nullptr;
714+
715+
switch (dtorKind) {
716+
case QualType::DK_none:
717+
llvm_unreachable("no cleanup for trivially-destructible variable");
718+
719+
case QualType::DK_cxx_destructor:
720+
// If there's an NRVO flag on the emission, we need a different
721+
// cleanup.
722+
if (emission.NRVOFlag) {
723+
cgm.errorNYI(var->getSourceRange(), "emitAutoVarTypeCleanup: NRVO");
724+
return;
725+
}
726+
// Otherwise, this is handled below.
727+
break;
728+
729+
case QualType::DK_objc_strong_lifetime:
730+
case QualType::DK_objc_weak_lifetime:
731+
case QualType::DK_nontrivial_c_struct:
732+
cgm.errorNYI(var->getSourceRange(),
733+
"emitAutoVarTypeCleanup: other dtor kind");
734+
return;
735+
}
736+
737+
// If we haven't chosen a more specific destroyer, use the default.
738+
if (!destroyer)
739+
destroyer = getDestroyer(dtorKind);
740+
741+
assert(!cir::MissingFeatures::ehCleanupFlags());
742+
ehStack.pushCleanup<DestroyObject>(cleanupKind, addr, type, destroyer);
743+
}

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 96 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ namespace clang::CIRGen {
2626

2727
CIRGenFunction::CIRGenFunction(CIRGenModule &cgm, CIRGenBuilderTy &builder,
2828
bool suppressNewContext)
29-
: CIRGenTypeCache(cgm), cgm{cgm}, builder(builder) {}
29+
: CIRGenTypeCache(cgm), cgm{cgm}, builder(builder) {
30+
ehStack.setCGF(this);
31+
currentCleanupStackDepth = 0;
32+
assert(ehStack.getStackDepth() == 0);
33+
}
3034

3135
CIRGenFunction::~CIRGenFunction() {}
3236

@@ -227,6 +231,14 @@ void CIRGenFunction::LexicalScope::cleanup() {
227231
CIRGenBuilderTy &builder = cgf.builder;
228232
LexicalScope *localScope = cgf.curLexScope;
229233

234+
auto applyCleanup = [&]() {
235+
if (performCleanup) {
236+
// ApplyDebugLocation
237+
assert(!cir::MissingFeatures::generateDebugInfo());
238+
forceCleanup();
239+
}
240+
};
241+
230242
if (returnBlock != nullptr) {
231243
// Write out the return block, which loads the value from `__retval` and
232244
// issues the `cir.return`.
@@ -235,46 +247,93 @@ void CIRGenFunction::LexicalScope::cleanup() {
235247
(void)emitReturn(*returnLoc);
236248
}
237249

238-
mlir::Block *curBlock = builder.getBlock();
239-
if (isGlobalInit() && !curBlock)
240-
return;
241-
if (curBlock->mightHaveTerminator() && curBlock->getTerminator())
242-
return;
243-
244-
// Get rid of any empty block at the end of the scope.
245-
bool entryBlock = builder.getInsertionBlock()->isEntryBlock();
246-
if (!entryBlock && curBlock->empty()) {
247-
curBlock->erase();
248-
if (returnBlock != nullptr && returnBlock->getUses().empty())
249-
returnBlock->erase();
250-
return;
251-
}
252-
253-
// Reached the end of the scope.
254-
{
250+
auto insertCleanupAndLeave = [&](mlir::Block *insPt) {
255251
mlir::OpBuilder::InsertionGuard guard(builder);
256-
builder.setInsertionPointToEnd(curBlock);
252+
builder.setInsertionPointToEnd(insPt);
253+
254+
// If we still don't have a cleanup block, it means that `applyCleanup`
255+
// below might be able to get us one.
256+
mlir::Block *cleanupBlock = localScope->getCleanupBlock(builder);
257+
258+
// Leverage and defers to RunCleanupsScope's dtor and scope handling.
259+
applyCleanup();
260+
261+
// If we now have one after `applyCleanup`, hook it up properly.
262+
if (!cleanupBlock && localScope->getCleanupBlock(builder)) {
263+
cleanupBlock = localScope->getCleanupBlock(builder);
264+
builder.create<cir::BrOp>(insPt->back().getLoc(), cleanupBlock);
265+
if (!cleanupBlock->mightHaveTerminator()) {
266+
mlir::OpBuilder::InsertionGuard guard(builder);
267+
builder.setInsertionPointToEnd(cleanupBlock);
268+
builder.create<cir::YieldOp>(localScope->endLoc);
269+
}
270+
}
257271

258272
if (localScope->depth == 0) {
259273
// Reached the end of the function.
260274
if (returnBlock != nullptr) {
261-
if (returnBlock->getUses().empty())
275+
if (returnBlock->getUses().empty()) {
262276
returnBlock->erase();
263-
else {
277+
} else {
278+
// Thread return block via cleanup block.
279+
if (cleanupBlock) {
280+
for (mlir::BlockOperand &blockUse : returnBlock->getUses()) {
281+
cir::BrOp brOp = mlir::cast<cir::BrOp>(blockUse.getOwner());
282+
brOp.setSuccessor(cleanupBlock);
283+
}
284+
}
285+
264286
builder.create<cir::BrOp>(*returnLoc, returnBlock);
265287
return;
266288
}
267289
}
268290
emitImplicitReturn();
269291
return;
270292
}
271-
// Reached the end of a non-function scope. Some scopes, such as those
272-
// used with the ?: operator, can return a value.
273-
if (!localScope->isTernary() && !curBlock->mightHaveTerminator()) {
293+
294+
// End of any local scope != function
295+
// Ternary ops have to deal with matching arms for yielding types
296+
// and do return a value, it must do its own cir.yield insertion.
297+
if (!localScope->isTernary() && !insPt->mightHaveTerminator()) {
274298
!retVal ? builder.create<cir::YieldOp>(localScope->endLoc)
275299
: builder.create<cir::YieldOp>(localScope->endLoc, retVal);
276300
}
301+
};
302+
303+
// If a cleanup block has been created at some point, branch to it
304+
// and set the insertion point to continue at the cleanup block.
305+
// Terminators are then inserted either in the cleanup block or
306+
// inline in this current block.
307+
mlir::Block *cleanupBlock = localScope->getCleanupBlock(builder);
308+
if (cleanupBlock)
309+
insertCleanupAndLeave(cleanupBlock);
310+
311+
// Now deal with any pending block wrap up like implicit end of
312+
// scope.
313+
314+
mlir::Block *curBlock = builder.getBlock();
315+
if (isGlobalInit() && !curBlock)
316+
return;
317+
if (curBlock->mightHaveTerminator() && curBlock->getTerminator())
318+
return;
319+
320+
// Get rid of any empty block at the end of the scope.
321+
bool entryBlock = builder.getInsertionBlock()->isEntryBlock();
322+
if (!entryBlock && curBlock->empty()) {
323+
curBlock->erase();
324+
if (returnBlock != nullptr && returnBlock->getUses().empty())
325+
returnBlock->erase();
326+
return;
277327
}
328+
329+
// If there's a cleanup block, branch to it, nothing else to do.
330+
if (cleanupBlock) {
331+
builder.create<cir::BrOp>(curBlock->back().getLoc(), cleanupBlock);
332+
return;
333+
}
334+
335+
// No pre-existent cleanup block, emit cleanup code and yield/return.
336+
insertCleanupAndLeave(curBlock);
278337
}
279338

280339
cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) {
@@ -408,7 +467,19 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
408467
}
409468
}
410469

411-
void CIRGenFunction::finishFunction(SourceLocation endLoc) {}
470+
void CIRGenFunction::finishFunction(SourceLocation endLoc) {
471+
// Pop any cleanups that might have been associated with the
472+
// parameters. Do this in whatever block we're currently in; it's
473+
// important to do this before we enter the return block or return
474+
// edges will be *really* confused.
475+
// TODO(cir): Use prologueCleanupDepth here.
476+
bool hasCleanups = ehStack.getStackDepth() != currentCleanupStackDepth;
477+
if (hasCleanups) {
478+
assert(!cir::MissingFeatures::generateDebugInfo());
479+
// FIXME(cir): should we clearInsertionPoint? breaks many testcases
480+
popCleanupBlocks(currentCleanupStackDepth);
481+
}
482+
}
412483

413484
mlir::LogicalResult CIRGenFunction::emitFunctionBody(const clang::Stmt *body) {
414485
auto result = mlir::LogicalResult::success();

0 commit comments

Comments
 (0)