282 lines · c
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-exception6//7//===----------------------------------------------------------------------===//8//9// These classes should be the minimum interface required for other parts of10// CIR CodeGen to emit cleanups. The implementation is in CIRGenCleanup.cpp and11// other implemenentation details that are not widely needed are in12// 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_H19#define CLANG_LIB_CIR_CODEGEN_EHSCOPESTACK_H20 21#include "clang/CIR/Dialect/IR/CIRDialect.h"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 a29/// label which hasn't been emitted yet. The goto is optimistically30/// emitted as a branch to the basic block for the label, and (if it31/// occurs in a scope with non-trivial cleanups) a fixup is added to32/// the innermost cleanup. When a (normal) cleanup is popped, any33/// unresolved fixups in that scope are threaded through the cleanup.34struct BranchFixup {35 /// The block containing the terminator which needs to be modified36 /// 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 was43 /// 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 exceptional55 /// 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 normal59 /// control flow (falling off the end of the scope, return, goto, ...).60 NormalCleanup = 0x2,61 62 NormalAndEHCleanup = EHCleanup | NormalCleanup,63 64 LifetimeMarker = 0x8,65 NormalEHLifetimeMarker = LifetimeMarker | NormalAndEHCleanup,66};67 68/// A stack of scopes which respond to exceptions, including cleanups69/// and catch blocks.70class EHScopeStack {71 friend class CIRGenFunction;72 73public:74 // TODO(ogcg): Switch to alignof(uint64_t) instead of 875 enum { ScopeStackAlignment = 8 };76 77 /// A saved depth on the scope stack. This is necessary because78 /// 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 be113 /// POD-like: cleanups will not be destructed, and they will be114 /// allocated on the cleanup stack and freely copied and moved115 /// around.116 ///117 /// Cleanup implementations should generally be declared in an118 /// 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;125 Cleanup(Cleanup &&) {}126 Cleanup() = default;127 128 virtual ~Cleanup() = default;129 130 /// Emit the cleanup. For normal cleanups, this is run in the131 /// same EH context as when the cleanup was pushed, i.e. the132 /// immediately-enclosing context of the cleanup scope. For133 /// EH cleanups, this is run in a terminate context.134 ///135 // \param flags cleanup kind.136 virtual void emit(CIRGenFunction &cgf) = 0;137 };138 139private:140 // The implementation for this class is in CIRGenCleanup.h and141 // CIRGenCleanup.cpp; the definition is here because it's used as a142 // member of CIRGenFunction.143 144 /// The start of the scope-stack buffer, i.e. the allocated pointer145 /// for the buffer. All of these pointers are either simultaneously146 /// null or simultaneously valid.147 std::unique_ptr<char[]> startOfBuffer;148 149 /// The end of the buffer.150 char *endOfBuffer = nullptr;151 152 /// The first valid entry in the buffer.153 char *startOfData = nullptr;154 155 /// The innermost normal cleanup on the stack.156 stable_iterator innermostNormalCleanup = stable_end();157 158 /// The innermost EH scope on the stack.159 stable_iterator innermostEHScope = stable_end();160 161 /// The CGF this Stack belong to162 CIRGenFunction *cgf = nullptr;163 164 /// The current set of branch fixups. A branch fixup is a jump to165 /// an as-yet unemitted label, i.e. a label for which we don't yet166 /// know the EH stack depth. Whenever we pop a cleanup, we have167 /// to thread all the current branch fixups through it.168 ///169 /// Fixups are recorded as the Use of the respective branch or170 /// switch statement. The use points to the final destination.171 /// When popping out of a cleanup, these uses are threaded through172 /// the cleanup and adjusted to point to the new cleanup.173 ///174 /// Note that branches are allowed to jump into protected scopes175 /// in certain situations; e.g. the following code is legal:176 /// struct A { ~A(); }; // trivial ctor, non-trivial dtor177 /// goto foo;178 /// A a;179 /// foo:180 /// bar();181 llvm::SmallVector<BranchFixup> branchFixups;182 183 // This class uses a custom allocator for maximum efficiency because cleanups184 // are allocated and freed very frequently. It's basically a bump pointer185 // allocator, but we can't use LLVM's BumpPtrAllocator because we use offsets186 // into the buffer as stable iterators.187 char *allocate(size_t size);188 void deallocate(size_t size);189 190 void *pushCleanup(CleanupKind kind, size_t dataSize);191 192public:193 EHScopeStack() = default;194 ~EHScopeStack() = default;195 196 /// Push a lazily-created cleanup on the stack.197 template <class T, class... As> void pushCleanup(CleanupKind kind, As... a) {198 static_assert(alignof(T) <= ScopeStackAlignment,199 "Cleanup's alignment is too large.");200 void *buffer = pushCleanup(kind, sizeof(T));201 [[maybe_unused]] Cleanup *obj = new (buffer) T(a...);202 }203 204 void setCGF(CIRGenFunction *inCGF) { cgf = inCGF; }205 206 /// Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp.207 void popCleanup();208 209 /// Push a set of catch handlers on the stack. The catch is210 /// uninitialized and will need to have the given number of handlers211 /// set on it.212 class EHCatchScope *pushCatch(unsigned numHandlers);213 214 /// Pops a catch scope off the stack. This is private to CIRGenException.cpp.215 void popCatch();216 217 /// Determines whether the exception-scopes stack is empty.218 bool empty() const { return startOfData == endOfBuffer; }219 220 bool requiresCatchOrCleanup() const;221 222 /// Determines whether there are any normal cleanups on the stack.223 bool hasNormalCleanups() const {224 return innermostNormalCleanup != stable_end();225 }226 227 /// Returns the innermost normal cleanup on the stack, or228 /// stable_end() if there are no normal cleanups.229 stable_iterator getInnermostNormalCleanup() const {230 return innermostNormalCleanup;231 }232 stable_iterator getInnermostActiveNormalCleanup() const;233 234 stable_iterator getInnermostEHScope() const { return innermostEHScope; }235 236 /// An unstable reference to a scope-stack depth. Invalidated by237 /// pushes but not pops.238 class iterator;239 240 /// Returns an iterator pointing to the innermost EH scope.241 iterator begin() const;242 243 /// Returns an iterator pointing to the outermost EH scope.244 iterator end() const;245 246 /// Create a stable reference to the top of the EH stack. The247 /// returned reference is valid until that scope is popped off the248 /// stack.249 stable_iterator stable_begin() const {250 return stable_iterator(endOfBuffer - startOfData);251 }252 253 /// Create a stable reference to the bottom of the EH stack.254 static stable_iterator stable_end() { return stable_iterator(0); }255 256 /// Turn a stable reference to a scope depth into a unstable pointer257 /// to the EH stack.258 iterator find(stable_iterator savePoint) const;259 260 /// Add a branch fixup to the current cleanup scope.261 BranchFixup &addBranchFixup() {262 assert(hasNormalCleanups() && "adding fixup in scope without cleanups");263 branchFixups.push_back(BranchFixup());264 return branchFixups.back();265 }266 267 unsigned getNumBranchFixups() const { return branchFixups.size(); }268 BranchFixup &getBranchFixup(unsigned i) {269 assert(i < getNumBranchFixups());270 return branchFixups[i];271 }272 273 /// Pops lazily-removed fixups from the end of the list. This274 /// should only be called by procedures which have just popped a275 /// cleanup or resolved one or more fixups.276 void popNullFixups();277};278 279} // namespace clang::CIRGen280 281#endif // CLANG_LIB_CIR_CODEGEN_EHSCOPESTACK_H282