clang 22.0.0git
EHScopeStack.h
Go to the documentation of this file.
1//===-- EHScopeStack.h - Stack for cleanup CIR generation -------*- C++ -*-===//
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 should be the minimum interface required for other parts of
10// CIR CodeGen to emit cleanups. The implementation is in CIRGenCleanup.cpp and
11// other implemenentation details that are not widely needed are in
12// CIRGenCleanup.h.
13//
14// TODO(cir): this header should be shared between LLVM and CIR codegen.
15//
16//===----------------------------------------------------------------------===//
17
18#ifndef CLANG_LIB_CIR_CODEGEN_EHSCOPESTACK_H
19#define CLANG_LIB_CIR_CODEGEN_EHSCOPESTACK_H
20
22#include "llvm/ADT/SmallVector.h"
23
24namespace clang::CIRGen {
25
26class CIRGenFunction;
27
28/// A branch fixup. These are required when emitting a goto to a
29/// label which hasn't been emitted yet. The goto is optimistically
30/// emitted as a branch to the basic block for the label, and (if it
31/// occurs in a scope with non-trivial cleanups) a fixup is added to
32/// the innermost cleanup. When a (normal) cleanup is popped, any
33/// unresolved fixups in that scope are threaded through the cleanup.
35 /// The block containing the terminator which needs to be modified
36 /// into a switch if this fixup is resolved into the current scope.
37 /// If null, LatestBranch points directly to the destination.
38 mlir::Block *optimisticBranchBlock = nullptr;
39
40 /// The ultimate destination of the branch.
41 ///
42 /// This can be set to null to indicate that this fixup was
43 /// successfully resolved.
44 mlir::Block *destination = nullptr;
45
46 /// The destination index value.
47 unsigned destinationIndex = 0;
48
49 /// The initial branch of the fixup.
50 cir::BrOp initialBranch = {};
51};
52
53enum CleanupKind : unsigned {
54 /// Denotes a cleanup that should run when a scope is exited using exceptional
55 /// control flow (a throw statement leading to stack unwinding, ).
56 EHCleanup = 0x1,
57
58 /// Denotes a cleanup that should run when a scope is exited using normal
59 /// control flow (falling off the end of the scope, return, goto, ...).
61
63
66};
67
68/// A stack of scopes which respond to exceptions, including cleanups
69/// and catch blocks.
71 friend class CIRGenFunction;
72
73public:
74 // TODO(ogcg): Switch to alignof(uint64_t) instead of 8
75 enum { ScopeStackAlignment = 8 };
76
77 /// A saved depth on the scope stack. This is necessary because
78 /// pushing scopes onto the stack invalidates iterators.
79 class stable_iterator {
80 friend class EHScopeStack;
81
82 /// Offset from startOfData to endOfBuffer.
83 ptrdiff_t size = -1;
84
85 explicit stable_iterator(ptrdiff_t size) : size(size) {}
86
87 public:
88 static stable_iterator invalid() { return stable_iterator(-1); }
89 stable_iterator() = default;
90
91 bool isValid() const { return size >= 0; }
92
93 /// Returns true if this scope encloses I.
94 /// Returns false if I is invalid.
95 /// This scope must be valid.
96 bool encloses(stable_iterator other) const { return size <= other.size; }
97
98 /// Returns true if this scope strictly encloses I: that is,
99 /// if it encloses I and is not I.
100 /// Returns false is I is invalid.
101 /// This scope must be valid.
102 bool strictlyEncloses(stable_iterator I) const { return size < I.size; }
103
104 friend bool operator==(stable_iterator A, stable_iterator B) {
105 return A.size == B.size;
106 }
107 friend bool operator!=(stable_iterator A, stable_iterator B) {
108 return A.size != B.size;
109 }
110 };
111
112 /// Information for lazily generating a cleanup. Subclasses must be
113 /// POD-like: cleanups will not be destructed, and they will be
114 /// allocated on the cleanup stack and freely copied and moved
115 /// around.
116 ///
117 /// Cleanup implementations should generally be declared in an
118 /// anonymous namespace.
119 class LLVM_MOVABLE_POLYMORPHIC_TYPE Cleanup {
120 // Anchor the construction vtable.
121 virtual void anchor();
122
123 public:
124 Cleanup(const Cleanup &) = default;
126 Cleanup() = default;
127
128 virtual ~Cleanup() = default;
129
130 /// Generation flags.
131 class Flags {
132 enum {
133 F_IsForEH = 0x1,
134 F_IsNormalCleanupKind = 0x2,
135 F_IsEHCleanupKind = 0x4,
136 F_HasExitSwitch = 0x8,
137 };
138 unsigned flags = 0;
139
140 public:
141 Flags() = default;
142
143 /// isForEH - true if the current emission is for an EH cleanup.
144 bool isForEHCleanup() const { return flags & F_IsForEH; }
145 bool isForNormalCleanup() const { return !isForEHCleanup(); }
146 void setIsForEHCleanup() { flags |= F_IsForEH; }
147
148 bool isNormalCleanupKind() const { return flags & F_IsNormalCleanupKind; }
149 void setIsNormalCleanupKind() { flags |= F_IsNormalCleanupKind; }
150
151 /// isEHCleanupKind - true if the cleanup was pushed as an EH
152 /// cleanup.
153 bool isEHCleanupKind() const { return flags & F_IsEHCleanupKind; }
154 void setIsEHCleanupKind() { flags |= F_IsEHCleanupKind; }
155
156 bool hasExitSwitch() const { return flags & F_HasExitSwitch; }
157 void setHasExitSwitch() { flags |= F_HasExitSwitch; }
158 };
159
160 /// Emit the cleanup. For normal cleanups, this is run in the
161 /// same EH context as when the cleanup was pushed, i.e. the
162 /// immediately-enclosing context of the cleanup scope. For
163 /// EH cleanups, this is run in a terminate context.
164 ///
165 // \param flags cleanup kind.
166 virtual void emit(CIRGenFunction &cgf, Flags flags) = 0;
167 };
168
169private:
170 // The implementation for this class is in CIRGenCleanup.h and
171 // CIRGenCleanup.cpp; the definition is here because it's used as a
172 // member of CIRGenFunction.
173
174 /// The start of the scope-stack buffer, i.e. the allocated pointer
175 /// for the buffer. All of these pointers are either simultaneously
176 /// null or simultaneously valid.
177 std::unique_ptr<char[]> startOfBuffer;
178
179 /// The end of the buffer.
180 char *endOfBuffer = nullptr;
181
182 /// The first valid entry in the buffer.
183 char *startOfData = nullptr;
184
185 /// The innermost normal cleanup on the stack.
186 stable_iterator innermostNormalCleanup = stable_end();
187
188 /// The innermost EH scope on the stack.
189 stable_iterator innermostEHScope = stable_end();
190
191 /// The CGF this Stack belong to
192 CIRGenFunction *cgf = nullptr;
193
194 /// The current set of branch fixups. A branch fixup is a jump to
195 /// an as-yet unemitted label, i.e. a label for which we don't yet
196 /// know the EH stack depth. Whenever we pop a cleanup, we have
197 /// to thread all the current branch fixups through it.
198 ///
199 /// Fixups are recorded as the Use of the respective branch or
200 /// switch statement. The use points to the final destination.
201 /// When popping out of a cleanup, these uses are threaded through
202 /// the cleanup and adjusted to point to the new cleanup.
203 ///
204 /// Note that branches are allowed to jump into protected scopes
205 /// in certain situations; e.g. the following code is legal:
206 /// struct A { ~A(); }; // trivial ctor, non-trivial dtor
207 /// goto foo;
208 /// A a;
209 /// foo:
210 /// bar();
212
213 // This class uses a custom allocator for maximum efficiency because cleanups
214 // are allocated and freed very frequently. It's basically a bump pointer
215 // allocator, but we can't use LLVM's BumpPtrAllocator because we use offsets
216 // into the buffer as stable iterators.
217 char *allocate(size_t size);
218 void deallocate(size_t size);
219
220 void *pushCleanup(CleanupKind kind, size_t dataSize);
221
222public:
223 EHScopeStack() = default;
224 ~EHScopeStack() = default;
225
226 /// Push a lazily-created cleanup on the stack.
227 template <class T, class... As> void pushCleanup(CleanupKind kind, As... a) {
228 static_assert(alignof(T) <= ScopeStackAlignment,
229 "Cleanup's alignment is too large.");
230 void *buffer = pushCleanup(kind, sizeof(T));
231 [[maybe_unused]] Cleanup *obj = new (buffer) T(a...);
232 }
233
234 void setCGF(CIRGenFunction *inCGF) { cgf = inCGF; }
235
236 /// Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp.
238
239 /// Push a set of catch handlers on the stack. The catch is
240 /// uninitialized and will need to have the given number of handlers
241 /// set on it.
242 class EHCatchScope *pushCatch(unsigned numHandlers);
243
244 /// Pops a catch scope off the stack. This is private to CIRGenException.cpp.
245 void popCatch();
246
247 /// Determines whether the exception-scopes stack is empty.
248 bool empty() const { return startOfData == endOfBuffer; }
249
251
252 /// Determines whether there are any normal cleanups on the stack.
253 bool hasNormalCleanups() const {
254 return innermostNormalCleanup != stable_end();
255 }
256
257 /// Returns the innermost normal cleanup on the stack, or
258 /// stable_end() if there are no normal cleanups.
260 return innermostNormalCleanup;
261 }
263
264 stable_iterator getInnermostEHScope() const { return innermostEHScope; }
265
266 /// An unstable reference to a scope-stack depth. Invalidated by
267 /// pushes but not pops.
268 class iterator;
269
270 /// Returns an iterator pointing to the innermost EH scope.
271 iterator begin() const;
272
273 /// Returns an iterator pointing to the outermost EH scope.
274 iterator end() const;
275
276 /// Create a stable reference to the top of the EH stack. The
277 /// returned reference is valid until that scope is popped off the
278 /// stack.
280 return stable_iterator(endOfBuffer - startOfData);
281 }
282
283 /// Create a stable reference to the bottom of the EH stack.
285
286 /// Turn a stable reference to a scope depth into a unstable pointer
287 /// to the EH stack.
288 iterator find(stable_iterator savePoint) const;
289
290 /// Add a branch fixup to the current cleanup scope.
292 assert(hasNormalCleanups() && "adding fixup in scope without cleanups");
293 branchFixups.push_back(BranchFixup());
294 return branchFixups.back();
295 }
296
297 unsigned getNumBranchFixups() const { return branchFixups.size(); }
299 assert(i < getNumBranchFixups());
300 return branchFixups[i];
301 }
302
303 /// Pops lazily-removed fixups from the end of the list. This
304 /// should only be called by procedures which have just popped a
305 /// cleanup or resolved one or more fixups.
307};
308
309} // namespace clang::CIRGen
310
311#endif // CLANG_LIB_CIR_CODEGEN_EHSCOPESTACK_H
__PTRDIFF_TYPE__ ptrdiff_t
A signed integer type that is the result of subtracting two pointers.
A scope which attempts to handle some, possibly all, types of exceptions.
bool isEHCleanupKind() const
isEHCleanupKind - true if the cleanup was pushed as an EH cleanup.
bool isForEHCleanup() const
isForEH - true if the current emission is for an EH cleanup.
Information for lazily generating a cleanup.
Cleanup(const Cleanup &)=default
virtual void emit(CIRGenFunction &cgf, Flags flags)=0
Emit the cleanup.
A saved depth on the scope stack.
bool strictlyEncloses(stable_iterator I) const
Returns true if this scope strictly encloses I: that is, if it encloses I and is not I.
bool encloses(stable_iterator other) const
Returns true if this scope encloses I.
friend bool operator==(stable_iterator A, stable_iterator B)
friend bool operator!=(stable_iterator A, stable_iterator B)
void setCGF(CIRGenFunction *inCGF)
void popCleanup()
Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp.
void popCatch()
Pops a catch scope off the stack. This is private to CIRGenException.cpp.
bool hasNormalCleanups() const
Determines whether there are any normal cleanups on the stack.
iterator find(stable_iterator savePoint) const
Turn a stable reference to a scope depth into a unstable pointer to the EH stack.
stable_iterator stable_begin() const
Create a stable reference to the top of the EH stack.
void pushCleanup(CleanupKind kind, As... a)
Push a lazily-created cleanup on the stack.
bool empty() const
Determines whether the exception-scopes stack is empty.
void popNullFixups()
Pops lazily-removed fixups from the end of the list.
iterator end() const
Returns an iterator pointing to the outermost EH scope.
class EHCatchScope * pushCatch(unsigned numHandlers)
Push a set of catch handlers on the stack.
bool requiresCatchOrCleanup() const
stable_iterator getInnermostActiveNormalCleanup() const
stable_iterator getInnermostEHScope() const
unsigned getNumBranchFixups() const
static stable_iterator stable_end()
Create a stable reference to the bottom of the EH stack.
BranchFixup & getBranchFixup(unsigned i)
BranchFixup & addBranchFixup()
Add a branch fixup to the current cleanup scope.
iterator begin() const
Returns an iterator pointing to the innermost EH scope.
stable_iterator getInnermostNormalCleanup() const
Returns the innermost normal cleanup on the stack, or stable_end() if there are no normal cleanups.
@ EHCleanup
Denotes a cleanup that should run when a scope is exited using exceptional control flow (a throw stat...
@ NormalCleanup
Denotes a cleanup that should run when a scope is exited using normal control flow (falling off the e...
const FunctionProtoType * T
cir::BrOp initialBranch
The initial branch of the fixup.
mlir::Block * destination
The ultimate destination of the branch.
mlir::Block * optimisticBranchBlock
The block containing the terminator which needs to be modified into a switch if this fixup is resolve...
unsigned destinationIndex
The destination index value.