Skip to content

Commit 7f22f5b

Browse files
authored
[CIR] Introduce more cleanup infrastructure (#152589)
Support for normal cleanups was introduced with a simplified implementation compared to what's in the incubator (which corresponds closely to the classic codegen implementation). This change introduces more of the infrastructure that will later be needed to handle non-trivial cleanup cases, including exception handling.
1 parent 74fbdbf commit 7f22f5b

File tree

4 files changed

+193
-26
lines changed

4 files changed

+193
-26
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ struct MissingFeatures {
200200
static bool deferredCXXGlobalInit() { return false; }
201201
static bool ehCleanupFlags() { return false; }
202202
static bool ehCleanupScope() { return false; }
203+
static bool ehCleanupScopeRequiresEHCleanup() { return false; }
204+
static bool ehCleanupBranchFixups() { return false; }
203205
static bool ehstackBranches() { return false; }
204206
static bool emitCheckedInBoundsGEP() { return false; }
205207
static bool emitCondLikelihoodViaExpectIntrinsic() { return false; }
@@ -213,6 +215,7 @@ struct MissingFeatures {
213215
static bool hip() { return false; }
214216
static bool implicitConstructorArgs() { return false; }
215217
static bool incrementProfileCounter() { return false; }
218+
static bool innermostEHScope() { return false; }
216219
static bool insertBuiltinUnpredictable() { return false; }
217220
static bool instrumentation() { return false; }
218221
static bool intrinsics() { return false; }

clang/lib/CIR/CodeGen/CIRGenCleanup.cpp

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -70,45 +70,101 @@ void EHScopeStack::deallocate(size_t size) {
7070
}
7171

7272
void *EHScopeStack::pushCleanup(CleanupKind kind, size_t size) {
73-
char *buffer = allocate(size);
73+
char *buffer = allocate(EHCleanupScope::getSizeForCleanupSize(size));
74+
bool isEHCleanup = kind & EHCleanup;
75+
bool isLifetimeMarker = kind & LifetimeMarker;
7476

75-
// When the full implementation is upstreamed, this will allocate
76-
// extra memory for and construct a wrapper object that is used to
77-
// manage the cleanup generation.
78-
assert(!cir::MissingFeatures::ehCleanupScope());
77+
assert(!cir::MissingFeatures::innermostEHScope());
7978

80-
return buffer;
79+
EHCleanupScope *scope = new (buffer) EHCleanupScope(size);
80+
81+
if (isLifetimeMarker)
82+
cgf->cgm.errorNYI("push lifetime marker cleanup");
83+
84+
// With Windows -EHa, Invoke llvm.seh.scope.begin() for EHCleanup
85+
if (cgf->getLangOpts().EHAsynch && isEHCleanup && !isLifetimeMarker &&
86+
cgf->getTarget().getCXXABI().isMicrosoft())
87+
cgf->cgm.errorNYI("push seh cleanup");
88+
89+
return scope->getCleanupBuffer();
90+
}
91+
92+
void EHScopeStack::popCleanup() {
93+
assert(!empty() && "popping exception stack when not empty");
94+
95+
assert(isa<EHCleanupScope>(*begin()));
96+
EHCleanupScope &cleanup = cast<EHCleanupScope>(*begin());
97+
deallocate(cleanup.getAllocatedSize());
98+
99+
// Destroy the cleanup.
100+
cleanup.destroy();
101+
102+
assert(!cir::MissingFeatures::ehCleanupBranchFixups());
81103
}
82104

83-
static mlir::Block *getCurCleanupBlock(CIRGenFunction &cgf) {
84-
mlir::OpBuilder::InsertionGuard guard(cgf.getBuilder());
85-
mlir::Block *cleanup =
86-
cgf.curLexScope->getOrCreateCleanupBlock(cgf.getBuilder());
87-
return cleanup;
105+
static void emitCleanup(CIRGenFunction &cgf, EHScopeStack::Cleanup *cleanup) {
106+
// Ask the cleanup to emit itself.
107+
assert(cgf.haveInsertPoint() && "expected insertion point");
108+
assert(!cir::MissingFeatures::ehCleanupFlags());
109+
cleanup->emit(cgf);
110+
assert(cgf.haveInsertPoint() && "cleanup ended with no insertion point?");
88111
}
89112

90113
/// Pops a cleanup block. If the block includes a normal cleanup, the
91114
/// current insertion point is threaded through the cleanup, as are
92115
/// any branch fixups on the cleanup.
93116
void CIRGenFunction::popCleanupBlock() {
94117
assert(!ehStack.empty() && "cleanup stack is empty!");
118+
assert(isa<EHCleanupScope>(*ehStack.begin()) && "top not a cleanup!");
119+
EHCleanupScope &scope = cast<EHCleanupScope>(*ehStack.begin());
120+
121+
// Remember activation information.
122+
bool isActive = scope.isActive();
123+
124+
assert(!cir::MissingFeatures::ehCleanupBranchFixups());
95125

96-
// The memory for the cleanup continues to be owned by the EHScopeStack
97-
// allocator, so we just destroy the object rather than attempting to
98-
// free it.
99-
EHScopeStack::Cleanup &cleanup = *ehStack.begin();
126+
// - whether there's a fallthrough
127+
mlir::Block *fallthroughSource = builder.getInsertionBlock();
128+
bool hasFallthrough = fallthroughSource != nullptr && isActive;
100129

101-
// The eventual implementation here will use the EHCleanupScope helper class.
102-
assert(!cir::MissingFeatures::ehCleanupScope());
130+
bool requiresNormalCleanup = scope.isNormalCleanup() && hasFallthrough;
103131

104-
mlir::OpBuilder::InsertionGuard guard(builder);
132+
// If we don't need the cleanup at all, we're done.
133+
assert(!cir::MissingFeatures::ehCleanupScopeRequiresEHCleanup());
134+
if (!requiresNormalCleanup) {
135+
ehStack.popCleanup();
136+
return;
137+
}
138+
139+
// Copy the cleanup emission data out. This uses either a stack
140+
// array or malloc'd memory, depending on the size, which is
141+
// behavior that SmallVector would provide, if we could use it
142+
// here. Unfortunately, if you ask for a SmallVector<char>, the
143+
// alignment isn't sufficient.
144+
auto *cleanupSource = reinterpret_cast<char *>(scope.getCleanupBuffer());
145+
alignas(EHScopeStack::ScopeStackAlignment) char
146+
cleanupBufferStack[8 * sizeof(void *)];
147+
std::unique_ptr<char[]> cleanupBufferHeap;
148+
size_t cleanupSize = scope.getCleanupSize();
149+
EHScopeStack::Cleanup *cleanup;
150+
151+
// This is necessary because we are going to deallocate the cleanup
152+
// (in popCleanup) before we emit it.
153+
if (cleanupSize <= sizeof(cleanupBufferStack)) {
154+
memcpy(cleanupBufferStack, cleanupSource, cleanupSize);
155+
cleanup = reinterpret_cast<EHScopeStack::Cleanup *>(cleanupBufferStack);
156+
} else {
157+
cleanupBufferHeap.reset(new char[cleanupSize]);
158+
memcpy(cleanupBufferHeap.get(), cleanupSource, cleanupSize);
159+
cleanup =
160+
reinterpret_cast<EHScopeStack::Cleanup *>(cleanupBufferHeap.get());
161+
}
105162

106163
assert(!cir::MissingFeatures::ehCleanupFlags());
107-
mlir::Block *cleanupEntry = getCurCleanupBlock(*this);
108-
builder.setInsertionPointToEnd(cleanupEntry);
109-
cleanup.emit(*this);
110164

111-
ehStack.deallocate(cleanup.getSize());
165+
ehStack.popCleanup();
166+
scope.markEmitted();
167+
emitCleanup(*this, cleanup);
112168
}
113169

114170
/// Pops cleanup blocks until the given savepoint is reached.

clang/lib/CIR/CodeGen/CIRGenCleanup.h

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,111 @@
1414
#ifndef CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H
1515
#define CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H
1616

17+
#include "Address.h"
1718
#include "EHScopeStack.h"
19+
#include "mlir/IR/Value.h"
1820

1921
namespace clang::CIRGen {
2022

23+
/// A protected scope for zero-cost EH handling.
24+
class EHScope {
25+
class CommonBitFields {
26+
friend class EHScope;
27+
unsigned kind : 3;
28+
};
29+
enum { NumCommonBits = 3 };
30+
31+
protected:
32+
class CleanupBitFields {
33+
friend class EHCleanupScope;
34+
unsigned : NumCommonBits;
35+
36+
/// Whether this cleanup needs to be run along normal edges.
37+
unsigned isNormalCleanup : 1;
38+
39+
/// Whether this cleanup needs to be run along exception edges.
40+
unsigned isEHCleanup : 1;
41+
42+
/// Whether this cleanup is currently active.
43+
unsigned isActive : 1;
44+
45+
/// Whether this cleanup is a lifetime marker
46+
unsigned isLifetimeMarker : 1;
47+
48+
/// Whether the normal cleanup should test the activation flag.
49+
unsigned testFlagInNormalCleanup : 1;
50+
51+
/// Whether the EH cleanup should test the activation flag.
52+
unsigned testFlagInEHCleanup : 1;
53+
54+
/// The amount of extra storage needed by the Cleanup.
55+
/// Always a multiple of the scope-stack alignment.
56+
unsigned cleanupSize : 12;
57+
};
58+
59+
union {
60+
CommonBitFields commonBits;
61+
CleanupBitFields cleanupBits;
62+
};
63+
64+
public:
65+
enum Kind { Cleanup, Catch, Terminate, Filter };
66+
67+
EHScope(Kind kind) { commonBits.kind = kind; }
68+
69+
Kind getKind() const { return static_cast<Kind>(commonBits.kind); }
70+
};
71+
72+
/// A cleanup scope which generates the cleanup blocks lazily.
73+
class alignas(EHScopeStack::ScopeStackAlignment) EHCleanupScope
74+
: public EHScope {
75+
public:
76+
/// Gets the size required for a lazy cleanup scope with the given
77+
/// cleanup-data requirements.
78+
static size_t getSizeForCleanupSize(size_t size) {
79+
return sizeof(EHCleanupScope) + size;
80+
}
81+
82+
size_t getAllocatedSize() const {
83+
return sizeof(EHCleanupScope) + cleanupBits.cleanupSize;
84+
}
85+
86+
EHCleanupScope(unsigned cleanupSize) : EHScope(EHScope::Cleanup) {
87+
// TODO(cir): When exception handling is upstreamed, isNormalCleanup and
88+
// isEHCleanup will be arguments to the constructor.
89+
cleanupBits.isNormalCleanup = true;
90+
cleanupBits.isEHCleanup = false;
91+
cleanupBits.isActive = true;
92+
cleanupBits.isLifetimeMarker = false;
93+
cleanupBits.testFlagInNormalCleanup = false;
94+
cleanupBits.testFlagInEHCleanup = false;
95+
cleanupBits.cleanupSize = cleanupSize;
96+
97+
assert(cleanupBits.cleanupSize == cleanupSize && "cleanup size overflow");
98+
}
99+
100+
void destroy() {}
101+
// Objects of EHCleanupScope are not destructed. Use destroy().
102+
~EHCleanupScope() = delete;
103+
104+
bool isNormalCleanup() const { return cleanupBits.isNormalCleanup; }
105+
106+
bool isActive() const { return cleanupBits.isActive; }
107+
108+
size_t getCleanupSize() const { return cleanupBits.cleanupSize; }
109+
void *getCleanupBuffer() { return this + 1; }
110+
111+
EHScopeStack::Cleanup *getCleanup() {
112+
return reinterpret_cast<EHScopeStack::Cleanup *>(getCleanupBuffer());
113+
}
114+
115+
static bool classof(const EHScope *scope) {
116+
return (scope->getKind() == Cleanup);
117+
}
118+
119+
void markEmitted() {}
120+
};
121+
21122
/// A non-stable pointer into the scope stack.
22123
class EHScopeStack::iterator {
23124
char *ptr = nullptr;
@@ -28,11 +129,9 @@ class EHScopeStack::iterator {
28129
public:
29130
iterator() = default;
30131

31-
EHScopeStack::Cleanup *get() const {
32-
return reinterpret_cast<EHScopeStack::Cleanup *>(ptr);
33-
}
132+
EHScope *get() const { return reinterpret_cast<EHScope *>(ptr); }
34133

35-
EHScopeStack::Cleanup &operator*() const { return *get(); }
134+
EHScope &operator*() const { return *get(); }
36135
};
37136

38137
inline EHScopeStack::iterator EHScopeStack::begin() const {

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,15 @@ class CIRGenFunction : public CIRGenTypeCache {
337337

338338
const clang::LangOptions &getLangOpts() const { return cgm.getLangOpts(); }
339339

340+
/// True if an insertion point is defined. If not, this indicates that the
341+
/// current code being emitted is unreachable.
342+
/// FIXME(cir): we need to inspect this and perhaps use a cleaner mechanism
343+
/// since we don't yet force null insertion point to designate behavior (like
344+
/// LLVM's codegen does) and we probably shouldn't.
345+
bool haveInsertPoint() const {
346+
return builder.getInsertionBlock() != nullptr;
347+
}
348+
340349
// Wrapper for function prototype sources. Wraps either a FunctionProtoType or
341350
// an ObjCMethodDecl.
342351
struct PrototypeWrapper {

0 commit comments

Comments
 (0)