//===-- gen/trycatchfinally.h - Try/catch/finally scopes --------*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #pragma once #include "dmd/globals.h" #include #include class Identifier; struct IRState; class TryCatchStatement; namespace llvm { class AllocaInst; class BasicBlock; class GlobalVariable; class MDNode; class Value; } /// Represents a position on the stack of currently active cleanup scopes. /// /// Since we always need to run a contiguous part of the stack (or all) in /// order, two cursors (one of which is usually the currently top of the stack) /// are enough to identify a sequence of cleanups to run. using CleanupCursor = size_t; //////////////////////////////////////////////////////////////////////////////// class TryCatchFinallyScopes; /// Represents a scope for a TryCatchStatement. class TryCatchScope { public: /// Stores information to be able to branch to a catch clause if it matches. /// /// Each catch body is emitted only once, but may be target from many landing /// pads (in case of nested catch or cleanup scopes). struct CatchBlock { /// The ClassInfo reference corresponding to the type to match the /// exception object against. llvm::GlobalVariable *classInfoPtr; /// The block to branch to if the exception type matches. llvm::BasicBlock *bodyBB; // PGO branch weights for the exception type match branch. // (first weight is for match, second is for mismatch) llvm::MDNode *branchWeights; }; /// The catch bodies are emitted when constructing a TryCatchScope (before the /// specified `endbb` block, which should be the try continuation block). TryCatchScope(IRState &irs, llvm::Value *ehPtrSlot, TryCatchStatement *stmt, llvm::BasicBlock *endbb); CleanupCursor getCleanupScope() const { return cleanupScope; } bool isCatchingNonExceptions() const { return catchesNonExceptions; } /// Returns the list of catch blocks, needed for landing pad emission. const std::vector &getCatchBlocks() const; private: TryCatchStatement *stmt; llvm::BasicBlock *endbb; CleanupCursor cleanupScope; bool catchesNonExceptions; std::vector catchBlocks; void emitCatchBodies(IRState &irs, llvm::Value *ehPtrSlot); void emitCatchBodiesMSVC(IRState &irs, llvm::Value *ehPtrSlot); }; //////////////////////////////////////////////////////////////////////////////// /// Represents a scope (in abstract terms, not curly braces) that requires a /// piece of cleanup code to be run whenever it is left, whether as part of /// normal control flow or exception unwinding. /// /// This includes finally blocks (which are also generated by the frontend for /// running the destructors of non-temporary variables) and the destructors of /// temporaries (which are unfortunately not lowered by the frontend). /// /// Our goal is to only emit each cleanup once such as to avoid generating an /// exponential number of basic blocks/landing pads for handling all the /// different ways of exiting a deeply nested scope (consider e.g. ten /// local variables with destructors, each of which might throw itself). class CleanupScope { public: CleanupScope(llvm::BasicBlock *beginBlock, llvm::BasicBlock *endBlock); llvm::BasicBlock *run(IRState &irs, llvm::BasicBlock *sourceBlock, llvm::BasicBlock *continueWith); /// MSVC uses C++ exception handling that puts cleanup blocks into funclets. /// This means that we cannot use a branch selector and conditional branches /// at cleanup exit to continue with different targets. /// Instead we make a full copy of the cleanup code for every target. llvm::BasicBlock *runCopying(IRState &irs, llvm::BasicBlock *sourceBlock, llvm::BasicBlock *continueWith, llvm::BasicBlock *unwindTo = nullptr, llvm::Value *funclet = nullptr); llvm::BasicBlock *beginBlock() const { return blocks.front(); } llvm::BasicBlock *endBlock() const { return blocks.back(); } private: std::vector blocks; /// The branch selector variable, or null if not created yet. llvm::AllocaInst *branchSelector = nullptr; /// Describes a particular way to leave a cleanup scope and continue execution /// with another one. /// /// In general, there can be multiple ones (normal exit, early returns, /// breaks/continues, exceptions, and so on). struct CleanupExitTarget { explicit CleanupExitTarget(llvm::BasicBlock *t) : branchTarget(t) {} /// The target basic block to branch to after running the cleanup. llvm::BasicBlock *branchTarget = nullptr; /// The basic blocks that want to continue with this target after running /// the cleanup. We need to keep this information around so we can insert /// stores to the branch selector variable when converting from one to two /// targets. std::vector sourceBlocks; /// MSVC: The basic blocks that are executed when going this route std::vector cleanupBlocks; }; /// Stores all possible targets blocks after running this cleanup, along /// with what predecessors want to continue at that target. The index in /// the vector corresponds to the branch selector value for that target. // Note: This is of course a bad choice of data structure for many targets // complexity-wise. However, situations where this matters should be // exceedingly rare in both hand-written as well as generated code. std::vector exitTargets; }; //////////////////////////////////////////////////////////////////////////////// /// Keeps track of source and target label of a goto. /// /// Used if we cannot immediately emit all the code for a jump because we have /// not generated code for the target yet. struct GotoJump { // The location of the goto instruction, for error reporting. Loc sourceLoc; /// The basic block which contains the goto as its terminator. llvm::BasicBlock *sourceBlock; /// While we have not found the actual branch target, we might need to /// create a "fake" basic block in order to be able to execute the cleanups /// (we do not keep branching information around after leaving the scope). llvm::BasicBlock *tentativeTarget; /// The label to target with the goto. Identifier *targetLabel; }; //////////////////////////////////////////////////////////////////////////////// /// Manages both try/catch and cleanups (try/finally blocks, destructors) /// stacks. /// /// Note that the entire code generation process, and this class in particular, /// depends heavily on the fact that we visit the statement/expression tree in /// its natural order, i.e. depth-first and in lexical order. In other words, /// the code here expects that after a cleanup/catch/etc. has been pushed, /// the contents of the block are generated, and it is then popped again /// afterwards. This is also encoded in the fact that none of the methods for /// branching/running cleanups take a cursor for describing the "source" scope, /// it is always assumed to be the current one. class TryCatchFinallyScopes { public: explicit TryCatchFinallyScopes(IRState &irs); ~TryCatchFinallyScopes(); bool empty() const { return tryCatchScopes.empty() && cleanupScopes.empty(); } /// Registers a try/catch scope. /// The catch bodies are emitted just before registering the new scope. void pushTryCatch(TryCatchStatement *stmt, llvm::BasicBlock *endbb); /// Unregisters the last registered try/catch scope. void popTryCatch(); /// Indicates whether there are any active catch blocks that handle /// non-Exception Throwables. bool isCatchingNonExceptions() const; /// Registers a piece of cleanup code to be run. /// /// The end block is expected not to contain a terminator yet. It will be /// added as needed, based on what follow-up blocks code from within this /// scope will branch to. void pushCleanup(llvm::BasicBlock *beginBlock, llvm::BasicBlock *endBlock); /// Terminates the current basic block with a branch to the cleanups needed /// for leaving the current scope and continuing execution at the target /// scope stack level. /// /// After running them, execution will branch to the given basic block. void runCleanups(CleanupCursor targetScope, llvm::BasicBlock *continueWith); /// Pops all the cleanups between the current scope and the target cursor. /// /// This does not insert any cleanup calls, use #runCleanups() beforehand. void popCleanups(CleanupCursor targetScope); /// Returns a cursor that identifies the current cleanup scope, to be later /// used with #runCleanups() et al. /// /// Note that this cursor is only valid as long as the current scope is not /// popped. CleanupCursor currentCleanupScope() const { return cleanupScopes.size(); } /// Registers a goto jump to a not yet visited label. /// /// TryCatchFinallyScopes needs to keep track of all existing cleanups which /// are popped before the goto target is resolved. These cleanups will be run /// at each goto site before jumping to the actual target. void registerUnresolvedGoto(Loc loc, Identifier *labelName); /// Resolves all unresolved gotos matching the specified label and makes sure /// they jump to the specified target block. void tryResolveGotos(Identifier *labelName, llvm::BasicBlock *targetBlock); /// Gets the landing pad for the current catches and cleanups. /// If there's no cached one, a new one will be emitted. llvm::BasicBlock *getLandingPad(); private: IRState &irs; llvm::AllocaInst *ehPtrSlot = nullptr; /// Similar story to ehPtrSlot, but for the selector value. llvm::AllocaInst *ehSelectorSlot = nullptr; llvm::BasicBlock *resumeUnwindBlock = nullptr; std::vector tryCatchScopes; /// cleanupScopes[i] contains the information to go from /// currentCleanupScope() == i + 1 to currentCleanupScope() == i. std::vector cleanupScopes; /// Keeps track of all the gotos originating from somewhere inside a cleanup /// scope for which we have not found the label yet (because it occurs /// lexically later in the function). // Note: Should also be a dense map from source block to the rest of the // data if we expect many gotos. using Gotos = std::vector; /// The first element represents the stack of unresolved top-level gotos /// (no cleanups). std::vector unresolvedGotosPerCleanupScope; /// Gets the unresolved gotos for the current cleanup scope. std::vector ¤tUnresolvedGotos(); using LandingPads = std::vector; /// Landing pads are cached via a dedicated stack for each cleanup scope (one /// element is pushed to/popped from the back on entering/leaving a try-catch /// block). /// The first element represents the stack of top-level landing pads (no /// cleanups). std::vector landingPadsPerCleanupScope; llvm::BasicBlock *&getLandingPadRef(CleanupCursor scope); /// Emits a landing pad to honor all the active cleanups and catches. llvm::BasicBlock *emitLandingPad(); /// Internal version that allows specifying the scope at which to start /// emitting the cleanups. void runCleanups(CleanupCursor sourceScope, CleanupCursor targetScope, llvm::BasicBlock *continueWith); /// Returns the stack slot that contains the exception object pointer while a /// landing pad is active, lazily creating it as needed. /// /// This value must dominate all uses; first storing it, and then loading it /// when calling _d_eh_resume_unwind. If we take a select at the end of any /// cleanups on the way to the latter, the value must also dominate all other /// predecessors of the cleanup. Thus, we just use a single alloca in the /// entry BB of the function. llvm::AllocaInst *getOrCreateEhPtrSlot(); /// Returns the basic block with the call to the unwind resume function. /// /// Because of ehPtrSlot, we do not need more than one, so might as well /// save on code size and reuse it. llvm::BasicBlock *getOrCreateResumeUnwindBlock(); // MSVC llvm::BasicBlock *emitLandingPadMSVC(CleanupCursor cleanupScope); void runCleanupCopies(CleanupCursor sourceScope, CleanupCursor targetScope, llvm::BasicBlock *continueWith); llvm::BasicBlock *runCleanupPad(CleanupCursor scope, llvm::BasicBlock *unwindTo); };