Skip to content

Commit e2b7057

Browse files
committed
[CIR] Upstream EHScopeStack memory allocator
When the cleanup handling code was initially upstreamed, a SmallVector was used to simplify the handling of the stack of cleanup objects. However, that mechanism won't scale well enough for the rate at which cleanup handlers are going to be pushed and popped while compiling a large program. This change introduces the custom memory allocator which is used in classic codegen and the CIR incubator. Thiis does not otherwise change the cleanup handling implementation and many parts of the infrastructure are still missing. This is not intended to have any observable effect on the generated CIR, but it does change the internal implementation significantly, so it's not exactly an NFC change. The functionality is covered by existing tests.
1 parent 1e2e903 commit e2b7057

File tree

8 files changed

+222
-26
lines changed

8 files changed

+222
-26
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ struct MissingFeatures {
199199
static bool dataLayoutTypeAllocSize() { return false; }
200200
static bool deferredCXXGlobalInit() { return false; }
201201
static bool ehCleanupFlags() { return false; }
202+
static bool ehCleanupScope() { return false; }
202203
static bool ehstackBranches() { return false; }
203204
static bool emitCheckedInBoundsGEP() { return false; }
204205
static bool emitCondLikelihoodViaExpectIntrinsic() { return false; }

clang/lib/CIR/CodeGen/CIRGenCleanup.cpp

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//
1717
//===----------------------------------------------------------------------===//
1818

19+
#include "CIRGenCleanup.h"
1920
#include "CIRGenFunction.h"
2021

2122
#include "clang/CIR/MissingFeatures.h"
@@ -33,6 +34,54 @@ using namespace clang::CIRGen;
3334

3435
void EHScopeStack::Cleanup::anchor() {}
3536

37+
/// Push an entry of the given size onto this protected-scope stack.
38+
char *EHScopeStack::allocate(size_t size) {
39+
size = llvm::alignTo(size, ScopeStackAlignment);
40+
if (!startOfBuffer) {
41+
unsigned capacity = 1024;
42+
while (capacity < size)
43+
capacity *= 2;
44+
startOfBuffer = new char[capacity];
45+
startOfData = endOfBuffer = startOfBuffer + capacity;
46+
} else if (static_cast<size_t>(startOfData - startOfBuffer) < size) {
47+
unsigned currentCapacity = endOfBuffer - startOfBuffer;
48+
unsigned usedCapacity = currentCapacity - (startOfData - startOfBuffer);
49+
50+
unsigned newCapacity = currentCapacity;
51+
do {
52+
newCapacity *= 2;
53+
} while (newCapacity < usedCapacity + size);
54+
55+
char *newStartOfBuffer = new char[newCapacity];
56+
char *newEndOfBuffer = newStartOfBuffer + newCapacity;
57+
char *newStartOfData = newEndOfBuffer - usedCapacity;
58+
memcpy(newStartOfData, startOfData, usedCapacity);
59+
delete[] startOfBuffer;
60+
startOfBuffer = newStartOfBuffer;
61+
endOfBuffer = newEndOfBuffer;
62+
startOfData = newStartOfData;
63+
}
64+
65+
assert(startOfBuffer + size <= startOfData);
66+
startOfData -= size;
67+
return startOfData;
68+
}
69+
70+
void EHScopeStack::deallocate(size_t size) {
71+
startOfData += llvm::alignTo(size, ScopeStackAlignment);
72+
}
73+
74+
void *EHScopeStack::pushCleanup(CleanupKind kind, size_t size) {
75+
char *buffer = allocate(size);
76+
77+
// When the full implementation is upstreamed, this will allocate
78+
// extra memory for and construct a wrapper object that is used to
79+
// manage the cleanup generation.
80+
assert(!cir::MissingFeatures::ehCleanupScope());
81+
82+
return buffer;
83+
}
84+
3685
static mlir::Block *getCurCleanupBlock(CIRGenFunction &cgf) {
3786
mlir::OpBuilder::InsertionGuard guard(cgf.getBuilder());
3887
mlir::Block *cleanup =
@@ -44,26 +93,34 @@ static mlir::Block *getCurCleanupBlock(CIRGenFunction &cgf) {
4493
/// current insertion point is threaded through the cleanup, as are
4594
/// any branch fixups on the cleanup.
4695
void CIRGenFunction::popCleanupBlock() {
47-
assert(!ehStack.cleanupStack.empty() && "cleanup stack is empty!");
96+
assert(!ehStack.empty() && "cleanup stack is empty!");
97+
98+
// The memory for the cleanup continues to be owned by the EHScopeStack
99+
// allocator, so we just destroy the object rather than attempting to
100+
// free it.
101+
EHScopeStack::Cleanup &cleanup = *ehStack.begin();
102+
103+
// The eventual implementation here will use the EHCleanupScope helper class.
104+
assert(!cir::MissingFeatures::ehCleanupScope());
105+
48106
mlir::OpBuilder::InsertionGuard guard(builder);
49-
std::unique_ptr<EHScopeStack::Cleanup> cleanup =
50-
ehStack.cleanupStack.pop_back_val();
51107

52108
assert(!cir::MissingFeatures::ehCleanupFlags());
53109
mlir::Block *cleanupEntry = getCurCleanupBlock(*this);
54110
builder.setInsertionPointToEnd(cleanupEntry);
55-
cleanup->emit(*this);
111+
cleanup.emit(*this);
112+
113+
ehStack.deallocate(cleanup.getSize());
56114
}
57115

58116
/// Pops cleanup blocks until the given savepoint is reached.
59-
void CIRGenFunction::popCleanupBlocks(size_t oldCleanupStackDepth) {
117+
void CIRGenFunction::popCleanupBlocks(
118+
EHScopeStack::stable_iterator oldCleanupStackDepth) {
60119
assert(!cir::MissingFeatures::ehstackBranches());
61120

62-
assert(ehStack.getStackDepth() >= oldCleanupStackDepth);
63-
64121
// Pop cleanup blocks until we reach the base stack depth for the
65122
// current scope.
66-
while (ehStack.getStackDepth() > oldCleanupStackDepth) {
123+
while (ehStack.stable_begin() != oldCleanupStackDepth) {
67124
popCleanupBlock();
68125
}
69126
}

clang/lib/CIR/CodeGen/CIRGenCleanup.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===----------------------------------------------------------------------===//
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+
// These classes support the generation of CIR for cleanups, initially based
10+
// on LLVM IR cleanup handling, but ought to change as CIR evolves.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef CLANG_LIB_CIR_CODEGEN_CGCLEANUP_H
15+
#define CLANG_LIB_CIR_CODEGEN_CGCLEANUP_H
16+
17+
#include "EHScopeStack.h"
18+
19+
namespace clang::CIRGen {
20+
21+
/// A non-stable pointer into the scope stack.
22+
class EHScopeStack::iterator {
23+
char *ptr;
24+
25+
friend class EHScopeStack;
26+
explicit iterator(char *ptr) : ptr(ptr) {}
27+
28+
public:
29+
iterator() : ptr(nullptr) {}
30+
31+
EHScopeStack::Cleanup *get() const {
32+
return reinterpret_cast<EHScopeStack::Cleanup *>(ptr);
33+
}
34+
35+
EHScopeStack::Cleanup &operator*() const { return *get(); }
36+
};
37+
38+
inline EHScopeStack::iterator EHScopeStack::begin() const {
39+
return iterator(startOfData);
40+
}
41+
42+
} // namespace clang::CIRGen
43+
#endif // CLANG_LIB_CIR_CODEGEN_CGCLEANUP_H

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,12 @@ struct DestroyObject final : EHScopeStack::Cleanup {
667667
void emit(CIRGenFunction &cgf) override {
668668
cgf.emitDestroy(addr, type, destroyer);
669669
}
670+
671+
// This is a placeholder until EHCleanupScope is implemented.
672+
size_t getSize() const override {
673+
assert(!cir::MissingFeatures::ehCleanupScope());
674+
return sizeof(DestroyObject);
675+
}
670676
};
671677
} // namespace
672678

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ CIRGenFunction::CIRGenFunction(CIRGenModule &cgm, CIRGenBuilderTy &builder,
2828
bool suppressNewContext)
2929
: CIRGenTypeCache(cgm), cgm{cgm}, builder(builder) {
3030
ehStack.setCGF(this);
31-
currentCleanupStackDepth = 0;
32-
assert(ehStack.getStackDepth() == 0);
3331
}
3432

3533
CIRGenFunction::~CIRGenFunction() {}
@@ -409,6 +407,8 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
409407
const auto *fd = dyn_cast_or_null<FunctionDecl>(d);
410408
curFuncDecl = d->getNonClosureContext();
411409

410+
prologueCleanupDepth = ehStack.stable_begin();
411+
412412
mlir::Block *entryBB = &fn.getBlocks().front();
413413
builder.setInsertionPointToStart(entryBB);
414414

@@ -475,11 +475,11 @@ void CIRGenFunction::finishFunction(SourceLocation endLoc) {
475475
// important to do this before we enter the return block or return
476476
// edges will be *really* confused.
477477
// TODO(cir): Use prologueCleanupDepth here.
478-
bool hasCleanups = ehStack.getStackDepth() != currentCleanupStackDepth;
478+
bool hasCleanups = ehStack.stable_begin() != prologueCleanupDepth;
479479
if (hasCleanups) {
480480
assert(!cir::MissingFeatures::generateDebugInfo());
481481
// FIXME(cir): should we clearInsertionPoint? breaks many testcases
482-
popCleanupBlocks(currentCleanupStackDepth);
482+
popCleanupBlocks(prologueCleanupDepth);
483483
}
484484
}
485485

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -601,9 +601,13 @@ class CIRGenFunction : public CIRGenTypeCache {
601601
FunctionArgList args, clang::SourceLocation loc,
602602
clang::SourceLocation startLoc);
603603

604+
/// The cleanup depth enclosing all the cleanups associated with the
605+
/// parameters.
606+
EHScopeStack::stable_iterator prologueCleanupDepth;
607+
604608
/// Takes the old cleanup stack size and emits the cleanup blocks
605609
/// that have been added.
606-
void popCleanupBlocks(size_t oldCleanupStackDepth);
610+
void popCleanupBlocks(EHScopeStack::stable_iterator oldCleanupStackDepth);
607611
void popCleanupBlock();
608612

609613
/// Push a cleanup to be run at the end of the current full-expression. Safe
@@ -622,7 +626,7 @@ class CIRGenFunction : public CIRGenTypeCache {
622626
/// Enters a new scope for capturing cleanups, all of which
623627
/// will be executed once the scope is exited.
624628
class RunCleanupsScope {
625-
size_t cleanupStackDepth, oldCleanupStackDepth;
629+
EHScopeStack::stable_iterator cleanupStackDepth, oldCleanupStackDepth;
626630

627631
protected:
628632
bool performCleanup;
@@ -638,7 +642,7 @@ class CIRGenFunction : public CIRGenTypeCache {
638642
/// Enter a new cleanup scope.
639643
explicit RunCleanupsScope(CIRGenFunction &cgf)
640644
: performCleanup(true), cgf(cgf) {
641-
cleanupStackDepth = cgf.ehStack.getStackDepth();
645+
cleanupStackDepth = cgf.ehStack.stable_begin();
642646
oldCleanupStackDepth = cgf.currentCleanupStackDepth;
643647
cgf.currentCleanupStackDepth = cleanupStackDepth;
644648
}
@@ -663,7 +667,7 @@ class CIRGenFunction : public CIRGenTypeCache {
663667
};
664668

665669
// Cleanup stack depth of the RunCleanupsScope that was pushed most recently.
666-
size_t currentCleanupStackDepth;
670+
EHScopeStack::stable_iterator currentCleanupStackDepth = ehStack.stable_end();
667671

668672
public:
669673
/// Represents a scope, including function bodies, compound statements, and

clang/lib/CIR/CodeGen/CIRGenStmt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
412412
auto *retBlock = curLexScope->getOrCreateRetBlock(*this, loc);
413413
// This should emit a branch through the cleanup block if one exists.
414414
builder.create<cir::BrOp>(loc, retBlock);
415-
if (ehStack.getStackDepth() != currentCleanupStackDepth)
415+
if (ehStack.stable_begin() != currentCleanupStackDepth)
416416
cgm.errorNYI(s.getSourceRange(), "return with cleanup stack");
417417
builder.createBlock(builder.getBlock()->getParent());
418418

clang/lib/CIR/CodeGen/EHScopeStack.h

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,47 @@ enum CleanupKind : unsigned {
4242
/// A stack of scopes which respond to exceptions, including cleanups
4343
/// and catch blocks.
4444
class EHScopeStack {
45+
friend class CIRGenFunction;
46+
4547
public:
48+
// TODO(ogcg): Switch to alignof(uint64_t) instead of 8
49+
enum { ScopeStackAlignment = 8 };
50+
51+
/// A saved depth on the scope stack. This is necessary because
52+
/// pushing scopes onto the stack invalidates iterators.
53+
class stable_iterator {
54+
friend class EHScopeStack;
55+
56+
/// Offset from startOfData to endOfBuffer.
57+
ptrdiff_t size;
58+
59+
stable_iterator(ptrdiff_t size) : size(size) {}
60+
61+
public:
62+
static stable_iterator invalid() { return stable_iterator(-1); }
63+
stable_iterator() : size(-1) {}
64+
65+
bool isValid() const { return size >= 0; }
66+
67+
/// Returns true if this scope encloses I.
68+
/// Returns false if I is invalid.
69+
/// This scope must be valid.
70+
bool encloses(stable_iterator other) const { return size <= other.size; }
71+
72+
/// Returns true if this scope strictly encloses I: that is,
73+
/// if it encloses I and is not I.
74+
/// Returns false is I is invalid.
75+
/// This scope must be valid.
76+
bool strictlyEncloses(stable_iterator I) const { return size < I.size; }
77+
78+
friend bool operator==(stable_iterator A, stable_iterator B) {
79+
return A.size == B.size;
80+
}
81+
friend bool operator!=(stable_iterator A, stable_iterator B) {
82+
return A.size != B.size;
83+
}
84+
};
85+
4686
/// Information for lazily generating a cleanup. Subclasses must be
4787
/// POD-like: cleanups will not be destructed, and they will be
4888
/// allocated on the cleanup stack and freely copied and moved
@@ -68,30 +108,75 @@ class EHScopeStack {
68108
///
69109
// \param flags cleanup kind.
70110
virtual void emit(CIRGenFunction &cgf) = 0;
71-
};
72111

73-
// Classic codegen has a finely tuned custom allocator and a complex stack
74-
// management scheme. We'll probably eventually want to find a way to share
75-
// that implementation. For now, we will use a very simplified implementation
76-
// to get cleanups working.
77-
llvm::SmallVector<std::unique_ptr<Cleanup>, 8> cleanupStack;
112+
// This is a placeholder until EHScope is implemented.
113+
virtual size_t getSize() const = 0;
114+
};
78115

79116
private:
117+
// The implementation for this class is in CIRGenCleanup.h and
118+
// CIRGenCleanup.cpp; the definition is here because it's used as a
119+
// member of CIRGenFunction.
120+
121+
/// The start of the scope-stack buffer, i.e. the allocated pointer
122+
/// for the buffer. All of these pointers are either simultaneously
123+
/// null or simultaneously valid.
124+
char *startOfBuffer = nullptr;
125+
126+
/// The end of the buffer.
127+
char *endOfBuffer = nullptr;
128+
129+
/// The first valid entry in the buffer.
130+
char *startOfData = nullptr;
131+
80132
/// The CGF this Stack belong to
81133
CIRGenFunction *cgf = nullptr;
82134

135+
// This class uses a custom allocator for maximum efficiency because cleanups
136+
// are allocated and freed very frequently. It's basically a bump pointer
137+
// allocator, but we can't use LLVM's BumpPtrAllocator because we use offsets
138+
// into the buffer as stable iterators.
139+
char *allocate(size_t size);
140+
void deallocate(size_t size);
141+
142+
void *pushCleanup(CleanupKind kind, size_t dataSize);
143+
83144
public:
84145
EHScopeStack() = default;
85-
~EHScopeStack() = default;
146+
~EHScopeStack() { delete[] startOfBuffer; }
86147

87148
/// Push a lazily-created cleanup on the stack.
88149
template <class T, class... As> void pushCleanup(CleanupKind kind, As... a) {
89-
cleanupStack.push_back(std::make_unique<T>(a...));
150+
static_assert(alignof(T) <= ScopeStackAlignment,
151+
"Cleanup's alignment is too large.");
152+
void *buffer = pushCleanup(kind, sizeof(T));
153+
[[maybe_unused]] Cleanup *obj = new (buffer) T(a...);
90154
}
91155

92156
void setCGF(CIRGenFunction *inCGF) { cgf = inCGF; }
93157

94-
size_t getStackDepth() const { return cleanupStack.size(); }
158+
/// Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp.
159+
void popCleanup();
160+
161+
/// Determines whether the exception-scopes stack is empty.
162+
bool empty() const { return startOfData == endOfBuffer; }
163+
164+
/// An unstable reference to a scope-stack depth. Invalidated by
165+
/// pushes but not pops.
166+
class iterator;
167+
168+
/// Returns an iterator pointing to the innermost EH scope.
169+
iterator begin() const;
170+
171+
/// Create a stable reference to the top of the EH stack. The
172+
/// returned reference is valid until that scope is popped off the
173+
/// stack.
174+
stable_iterator stable_begin() const {
175+
return stable_iterator(endOfBuffer - startOfData);
176+
}
177+
178+
/// Create a stable reference to the bottom of the EH stack.
179+
static stable_iterator stable_end() { return stable_iterator(0); }
95180
};
96181

97182
} // namespace clang::CIRGen

0 commit comments

Comments
 (0)