mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-01 15:40:55 +03:00
309 lines
12 KiB
C++
309 lines
12 KiB
C++
//===-- 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 <stddef.h>
|
||
#include <vector>
|
||
|
||
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<CatchBlock> &getCatchBlocks() const;
|
||
|
||
private:
|
||
TryCatchStatement *stmt;
|
||
llvm::BasicBlock *endbb;
|
||
CleanupCursor cleanupScope;
|
||
bool catchesNonExceptions;
|
||
|
||
std::vector<CatchBlock> 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<llvm::BasicBlock *> 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<llvm::BasicBlock *> sourceBlocks;
|
||
|
||
/// MSVC: The basic blocks that are executed when going this route
|
||
std::vector<llvm::BasicBlock *> 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<CleanupExitTarget> 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<TryCatchScope> tryCatchScopes;
|
||
|
||
/// cleanupScopes[i] contains the information to go from
|
||
/// currentCleanupScope() == i + 1 to currentCleanupScope() == i.
|
||
std::vector<CleanupScope> 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<GotoJump>;
|
||
/// The first element represents the stack of unresolved top-level gotos
|
||
/// (no cleanups).
|
||
std::vector<Gotos> unresolvedGotosPerCleanupScope;
|
||
|
||
/// Gets the unresolved gotos for the current cleanup scope.
|
||
std::vector<GotoJump> ¤tUnresolvedGotos();
|
||
|
||
using LandingPads = std::vector<llvm::BasicBlock *>;
|
||
/// 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<LandingPads> 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);
|
||
};
|