ldc/gen/trycatchfinally.h
2018-10-20 16:19:46 +02:00

309 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//===-- 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> &currentUnresolvedGotos();
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);
};