mirror of
https://github.com/intel/llvm.git
synced 2026-02-09 01:52:26 +08:00
[WebAssembly] Add assembly support for final EH proposal (#107917)
This adds the basic assembly generation support for the final EH proposal, which was newly adopted in Sep 2023 and advanced into Phase 4 in Jul 2024: https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md This adds support for the generation of new `try_table` and `throw_ref` instruction in .s asesmbly format. This does NOT yet include - Block annotation comment generation for .s format - .o object file generation - .s assembly parsing - Type checking (AsmTypeCheck) - Disassembler - Fixing unwind mismatches in CFGStackify These will be added as follow-up PRs. --- The format for `TRY_TABLE`, both for `MachineInstr` and `MCInst`, is as follows: ``` TRY_TABLE type number_of_catches catch_clauses* ``` where `catch_clause` is ``` catch_opcode tag+ destination ``` `catch_opcode` should be one of 0/1/2/3, which denotes `CATCH`/`CATCH_REF`/`CATCH_ALL`/`CATCH_ALL_REF` respectively. (See `BinaryFormat/Wasm.h`) `tag` exists when the catch is one of `CATCH` or `CATCH_REF`. The MIR format is printed as just the list of raw operands. The (stack-based) assembly instruction supports pretty-printing, including printing `catch` clauses by name, in InstPrinter. In addition to the new instructions `TRY_TABLE` and `THROW_REF`, this adds four pseudo instructions: `CATCH`, `CATCH_REF`, `CATCH_ALL`, and `CATCH_ALL_REF`. These are pseudo instructions to simulate block return values of `catch`, `catch_ref`, `catch_all`, `catch_all_ref` clauses in `try_table` respectively, given that we don't support block return values except for one case (`fixEndsAtEndOfFunction` in CFGStackify). These will be omitted when we lower the instructions to `MCInst` at the end. LateEHPrepare now will have one more stage to covert `CATCH`/`CATCH_ALL`s to `CATCH_REF`/`CATCH_ALL_REF`s when there is a `RETHROW` to rethrow its exception. The pass also converts `RETHROW`s into `THROW_REF`. Note that we still use `RETHROW` as an interim pseudo instruction until we convert them to `THROW_REF` in LateEHPrepare. CFGStackify has a new `placeTryTableMarker` function, which places `try_table`/`end_try_table` markers with a necessary `catch` clause and also `block`/`end_block` markers for the destination of the `catch` clause. In MCInstLower, now we need to support one more case for the multivalue block signature (`catch_ref`'s destination's `(i32, exnref)` return type). InstPrinter has a new routine to print the `catch_list` type, which is used to print `try_table` instructions. The new test, `exception.ll`'s source is the same as `exception-legacy.ll`, with the FileCheck expectations changed. One difference is the commands in this file have `-wasm-enable-exnref` to test the new format, and don't have `-wasm-disable-explicit-locals -wasm-keep-registers`, because the new custom InstPrinter routine to print `catch_list` only works for the stack-based instructions (`_S`), and we can't use `-wasm-keep-registers` for them. As in `exception-legacy.ll`, the FileCheck lines for the new tests do not contain the whole program; they mostly contain only the control flow instructions for readability.
This commit is contained in:
@@ -144,6 +144,14 @@ enum : unsigned {
|
||||
WASM_OPCODE_I32_RMW_CMPXCHG = 0x48,
|
||||
};
|
||||
|
||||
// Sub-opcodes for catch clauses in a try_table instruction
|
||||
enum : unsigned {
|
||||
WASM_OPCODE_CATCH = 0x00,
|
||||
WASM_OPCODE_CATCH_REF = 0x01,
|
||||
WASM_OPCODE_CATCH_ALL = 0x02,
|
||||
WASM_OPCODE_CATCH_ALL_REF = 0x03,
|
||||
};
|
||||
|
||||
enum : unsigned {
|
||||
WASM_LIMITS_FLAG_NONE = 0x0,
|
||||
WASM_LIMITS_FLAG_HAS_MAX = 0x1,
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace {
|
||||
/// WebAssemblyOperand - Instances of this class represent the operands in a
|
||||
/// parsed Wasm machine instruction.
|
||||
struct WebAssemblyOperand : public MCParsedAsmOperand {
|
||||
enum KindTy { Token, Integer, Float, Symbol, BrList } Kind;
|
||||
enum KindTy { Token, Integer, Float, Symbol, BrList, CatchList } Kind;
|
||||
|
||||
SMLoc StartLoc, EndLoc;
|
||||
|
||||
@@ -99,6 +99,7 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
|
||||
bool isMem() const override { return false; }
|
||||
bool isReg() const override { return false; }
|
||||
bool isBrList() const { return Kind == BrList; }
|
||||
bool isCatchList() const { return Kind == CatchList; }
|
||||
|
||||
MCRegister getReg() const override {
|
||||
llvm_unreachable("Assembly inspects a register operand");
|
||||
@@ -151,6 +152,10 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
|
||||
Inst.addOperand(MCOperand::createImm(Br));
|
||||
}
|
||||
|
||||
void addCatchListOperands(MCInst &Inst, unsigned N) const {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void print(raw_ostream &OS) const override {
|
||||
switch (Kind) {
|
||||
case Token:
|
||||
@@ -168,6 +173,9 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
|
||||
case BrList:
|
||||
OS << "BrList:" << BrL.List.size();
|
||||
break;
|
||||
case CatchList:
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -367,3 +367,44 @@ void WebAssemblyInstPrinter::printWebAssemblySignatureOperand(const MCInst *MI,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebAssemblyInstPrinter::printCatchList(const MCInst *MI, unsigned OpNo,
|
||||
raw_ostream &O) {
|
||||
unsigned OpIdx = OpNo;
|
||||
const MCOperand &Op = MI->getOperand(OpIdx++);
|
||||
unsigned NumCatches = Op.getImm();
|
||||
|
||||
auto PrintTagOp = [&](const MCOperand &Op) {
|
||||
const MCSymbolRefExpr *TagExpr = nullptr;
|
||||
const MCSymbolWasm *TagSym = nullptr;
|
||||
assert(Op.isExpr());
|
||||
TagExpr = dyn_cast<MCSymbolRefExpr>(Op.getExpr());
|
||||
TagSym = cast<MCSymbolWasm>(&TagExpr->getSymbol());
|
||||
O << TagSym->getName() << " ";
|
||||
};
|
||||
|
||||
for (unsigned I = 0; I < NumCatches; I++) {
|
||||
const MCOperand &Op = MI->getOperand(OpIdx++);
|
||||
O << "(";
|
||||
switch (Op.getImm()) {
|
||||
case wasm::WASM_OPCODE_CATCH:
|
||||
O << "catch ";
|
||||
PrintTagOp(MI->getOperand(OpIdx++));
|
||||
break;
|
||||
case wasm::WASM_OPCODE_CATCH_REF:
|
||||
O << "catch_ref ";
|
||||
PrintTagOp(MI->getOperand(OpIdx++));
|
||||
break;
|
||||
case wasm::WASM_OPCODE_CATCH_ALL:
|
||||
O << "catch_all ";
|
||||
break;
|
||||
case wasm::WASM_OPCODE_CATCH_ALL_REF:
|
||||
O << "catch_all_ref ";
|
||||
break;
|
||||
}
|
||||
O << MI->getOperand(OpIdx++).getImm(); // destination
|
||||
O << ")";
|
||||
if (I < NumCatches - 1)
|
||||
O << " ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
raw_ostream &O);
|
||||
void printWebAssemblySignatureOperand(const MCInst *MI, unsigned OpNo,
|
||||
raw_ostream &O);
|
||||
void printCatchList(const MCInst *MI, unsigned OpNo, raw_ostream &O);
|
||||
|
||||
// Autogenerated by tblgen.
|
||||
std::pair<const char *, uint64_t> getMnemonic(const MCInst *MI) override;
|
||||
|
||||
@@ -87,6 +87,8 @@ enum OperandType {
|
||||
OPERAND_BRLIST,
|
||||
/// 32-bit unsigned table number.
|
||||
OPERAND_TABLE,
|
||||
/// A list of catch clauses for try_table.
|
||||
OPERAND_CATCH_LIST,
|
||||
};
|
||||
} // end namespace WebAssembly
|
||||
|
||||
@@ -119,6 +121,10 @@ enum TOF {
|
||||
// address relative the __table_base wasm global.
|
||||
// Only applicable to function symbols.
|
||||
MO_TABLE_BASE_REL,
|
||||
|
||||
// On a block signature operand this indicates that this is a destination
|
||||
// block of a (catch_ref) clause in try_table.
|
||||
MO_CATCH_BLOCK_SIG,
|
||||
};
|
||||
|
||||
} // end namespace WebAssemblyII
|
||||
@@ -462,6 +468,22 @@ inline bool isMarker(unsigned Opc) {
|
||||
case WebAssembly::TRY_S:
|
||||
case WebAssembly::END_TRY:
|
||||
case WebAssembly::END_TRY_S:
|
||||
case WebAssembly::TRY_TABLE:
|
||||
case WebAssembly::TRY_TABLE_S:
|
||||
case WebAssembly::END_TRY_TABLE:
|
||||
case WebAssembly::END_TRY_TABLE_S:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isTry(unsigned Opc) {
|
||||
switch (Opc) {
|
||||
case WebAssembly::TRY:
|
||||
case WebAssembly::TRY_S:
|
||||
case WebAssembly::TRY_TABLE:
|
||||
case WebAssembly::TRY_TABLE_S:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -474,6 +496,14 @@ inline bool isCatch(unsigned Opc) {
|
||||
case WebAssembly::CATCH_LEGACY_S:
|
||||
case WebAssembly::CATCH_ALL_LEGACY:
|
||||
case WebAssembly::CATCH_ALL_LEGACY_S:
|
||||
case WebAssembly::CATCH:
|
||||
case WebAssembly::CATCH_S:
|
||||
case WebAssembly::CATCH_REF:
|
||||
case WebAssembly::CATCH_REF_S:
|
||||
case WebAssembly::CATCH_ALL:
|
||||
case WebAssembly::CATCH_ALL_S:
|
||||
case WebAssembly::CATCH_ALL_REF:
|
||||
case WebAssembly::CATCH_ALL_REF_S:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -33,11 +33,15 @@ enum class BlockType : unsigned {
|
||||
Externref = unsigned(wasm::ValType::EXTERNREF),
|
||||
Funcref = unsigned(wasm::ValType::FUNCREF),
|
||||
Exnref = unsigned(wasm::ValType::EXNREF),
|
||||
// Multivalue blocks (and other non-void blocks) are only emitted when the
|
||||
// blocks will never be exited and are at the ends of functions (see
|
||||
// WebAssemblyCFGStackify::fixEndsAtEndOfFunction). They also are never made
|
||||
// to pop values off the stack, so the exact multivalue signature can always
|
||||
// be inferred from the return type of the parent function in MCInstLower.
|
||||
// Multivalue blocks are emitted in two cases:
|
||||
// 1. When the blocks will never be exited and are at the ends of functions
|
||||
// (see WebAssemblyCFGStackify::fixEndsAtEndOfFunction). In this case the
|
||||
// exact multivalue signature can always be inferred from the return type
|
||||
// of the parent function.
|
||||
// 2. (catch_ref ...) clause in try_table instruction. Currently all tags we
|
||||
// support (cpp_exception and c_longjmp) throws a single i32, so the
|
||||
// multivalue signature for this case will be (i32, exnref).
|
||||
// The real multivalue siganture will be added in MCInstLower.
|
||||
Multivalue = 0xffff,
|
||||
};
|
||||
|
||||
|
||||
@@ -683,6 +683,17 @@ void WebAssemblyAsmPrinter::emitInstruction(const MachineInstr *MI) {
|
||||
// This is a compiler barrier that prevents instruction reordering during
|
||||
// backend compilation, and should not be emitted.
|
||||
break;
|
||||
case WebAssembly::CATCH:
|
||||
case WebAssembly::CATCH_S:
|
||||
case WebAssembly::CATCH_REF:
|
||||
case WebAssembly::CATCH_REF_S:
|
||||
case WebAssembly::CATCH_ALL:
|
||||
case WebAssembly::CATCH_ALL_S:
|
||||
case WebAssembly::CATCH_ALL_REF:
|
||||
case WebAssembly::CATCH_ALL_REF_S:
|
||||
// These are pseudo instructions to represent catch clauses in try_table
|
||||
// instruction to simulate block return values.
|
||||
break;
|
||||
default: {
|
||||
WebAssemblyMCInstLower MCInstLowering(OutContext, *this);
|
||||
MCInst TmpInst;
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
/// \file
|
||||
/// This file implements a CFG stacking pass.
|
||||
///
|
||||
/// This pass inserts BLOCK, LOOP, and TRY markers to mark the start of scopes,
|
||||
/// since scope boundaries serve as the labels for WebAssembly's control
|
||||
/// transfers.
|
||||
/// This pass inserts BLOCK, LOOP, TRY, and TRY_TABLE markers to mark the start
|
||||
/// of scopes, since scope boundaries serve as the labels for WebAssembly's
|
||||
/// control transfers.
|
||||
///
|
||||
/// This is sufficient to convert arbitrary CFGs into a form that works on
|
||||
/// WebAssembly, provided that all loops are single-entry.
|
||||
@@ -21,6 +21,7 @@
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
|
||||
#include "Utils/WebAssemblyTypeUtilities.h"
|
||||
#include "WebAssembly.h"
|
||||
#include "WebAssemblyExceptionInfo.h"
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "WebAssemblySubtarget.h"
|
||||
#include "WebAssemblyUtilities.h"
|
||||
#include "llvm/ADT/Statistic.h"
|
||||
#include "llvm/BinaryFormat/Wasm.h"
|
||||
#include "llvm/CodeGen/MachineDominators.h"
|
||||
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
||||
#include "llvm/CodeGen/MachineLoopInfo.h"
|
||||
@@ -74,6 +76,7 @@ class WebAssemblyCFGStackify final : public MachineFunctionPass {
|
||||
void placeBlockMarker(MachineBasicBlock &MBB);
|
||||
void placeLoopMarker(MachineBasicBlock &MBB);
|
||||
void placeTryMarker(MachineBasicBlock &MBB);
|
||||
void placeTryTableMarker(MachineBasicBlock &MBB);
|
||||
|
||||
// Exception handling related functions
|
||||
bool fixCallUnwindMismatches(MachineFunction &MF);
|
||||
@@ -97,11 +100,11 @@ class WebAssemblyCFGStackify final : public MachineFunctionPass {
|
||||
void fixEndsAtEndOfFunction(MachineFunction &MF);
|
||||
void cleanupFunctionData(MachineFunction &MF);
|
||||
|
||||
// For each BLOCK|LOOP|TRY, the corresponding END_(BLOCK|LOOP|TRY) or DELEGATE
|
||||
// (in case of TRY).
|
||||
// For each BLOCK|LOOP|TRY|TRY_TABLE, the corresponding
|
||||
// END_(BLOCK|LOOP|TRY|TRY_TABLE) or DELEGATE (in case of TRY).
|
||||
DenseMap<const MachineInstr *, MachineInstr *> BeginToEnd;
|
||||
// For each END_(BLOCK|LOOP|TRY) or DELEGATE, the corresponding
|
||||
// BLOCK|LOOP|TRY.
|
||||
// For each END_(BLOCK|LOOP|TRY|TRY_TABLE) or DELEGATE, the corresponding
|
||||
// BLOCK|LOOP|TRY|TRY_TABLE.
|
||||
DenseMap<const MachineInstr *, MachineInstr *> EndToBegin;
|
||||
// <TRY marker, EH pad> map
|
||||
DenseMap<const MachineInstr *, MachineBasicBlock *> TryToEHPad;
|
||||
@@ -150,9 +153,10 @@ public:
|
||||
} // end anonymous namespace
|
||||
|
||||
char WebAssemblyCFGStackify::ID = 0;
|
||||
INITIALIZE_PASS(WebAssemblyCFGStackify, DEBUG_TYPE,
|
||||
"Insert BLOCK/LOOP/TRY markers for WebAssembly scopes", false,
|
||||
false)
|
||||
INITIALIZE_PASS(
|
||||
WebAssemblyCFGStackify, DEBUG_TYPE,
|
||||
"Insert BLOCK/LOOP/TRY/TRY_TABLE markers for WebAssembly scopes", false,
|
||||
false)
|
||||
|
||||
FunctionPass *llvm::createWebAssemblyCFGStackify() {
|
||||
return new WebAssemblyCFGStackify();
|
||||
@@ -314,12 +318,13 @@ void WebAssemblyCFGStackify::placeBlockMarker(MachineBasicBlock &MBB) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// If there is a previously placed BLOCK/TRY marker and its corresponding
|
||||
// END marker is before the current BLOCK's END marker, that should be
|
||||
// placed after this BLOCK. Otherwise it should be placed before this BLOCK
|
||||
// marker.
|
||||
// If there is a previously placed BLOCK/TRY/TRY_TABLE marker and its
|
||||
// corresponding END marker is before the current BLOCK's END marker, that
|
||||
// should be placed after this BLOCK. Otherwise it should be placed before
|
||||
// this BLOCK marker.
|
||||
if (MI.getOpcode() == WebAssembly::BLOCK ||
|
||||
MI.getOpcode() == WebAssembly::TRY) {
|
||||
MI.getOpcode() == WebAssembly::TRY ||
|
||||
MI.getOpcode() == WebAssembly::TRY_TABLE) {
|
||||
if (BeginToEnd[&MI]->getParent()->getNumber() <= MBB.getNumber())
|
||||
AfterSet.insert(&MI);
|
||||
#ifndef NDEBUG
|
||||
@@ -329,10 +334,11 @@ void WebAssemblyCFGStackify::placeBlockMarker(MachineBasicBlock &MBB) {
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// All END_(BLOCK|LOOP|TRY) markers should be before the BLOCK.
|
||||
// All END_(BLOCK|LOOP|TRY|TRY_TABLE) markers should be before the BLOCK.
|
||||
if (MI.getOpcode() == WebAssembly::END_BLOCK ||
|
||||
MI.getOpcode() == WebAssembly::END_LOOP ||
|
||||
MI.getOpcode() == WebAssembly::END_TRY)
|
||||
MI.getOpcode() == WebAssembly::END_TRY ||
|
||||
MI.getOpcode() == WebAssembly::END_TRY_TABLE)
|
||||
BeforeSet.insert(&MI);
|
||||
#endif
|
||||
|
||||
@@ -374,6 +380,11 @@ void WebAssemblyCFGStackify::placeBlockMarker(MachineBasicBlock &MBB) {
|
||||
// loop is above this block's header, the END_LOOP should be placed after
|
||||
// the END_BLOCK, because the loop contains this block. Otherwise the
|
||||
// END_LOOP should be placed before the END_BLOCK. The same for END_TRY.
|
||||
//
|
||||
// Note that while there can be existing END_TRYs, there can't be
|
||||
// END_TRY_TABLEs; END_TRYs are placed when its corresponding EH pad is
|
||||
// processed, so they are placed below MBB (EH pad) in placeTryMarker. But
|
||||
// END_TRY_TABLE is placed like a END_BLOCK, so they can't be here already.
|
||||
if (MI.getOpcode() == WebAssembly::END_LOOP ||
|
||||
MI.getOpcode() == WebAssembly::END_TRY) {
|
||||
if (EndToBegin[&MI]->getParent()->getNumber() >= Header->getNumber())
|
||||
@@ -657,7 +668,251 @@ void WebAssemblyCFGStackify::placeTryMarker(MachineBasicBlock &MBB) {
|
||||
updateScopeTops(Header, End);
|
||||
}
|
||||
|
||||
void WebAssemblyCFGStackify::placeTryTableMarker(MachineBasicBlock &MBB) {
|
||||
assert(MBB.isEHPad());
|
||||
MachineFunction &MF = *MBB.getParent();
|
||||
auto &MDT = getAnalysis<MachineDominatorTreeWrapperPass>().getDomTree();
|
||||
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
|
||||
const auto &MLI = getAnalysis<MachineLoopInfoWrapperPass>().getLI();
|
||||
const auto &WEI = getAnalysis<WebAssemblyExceptionInfo>();
|
||||
SortRegionInfo SRI(MLI, WEI);
|
||||
const auto &MFI = *MF.getInfo<WebAssemblyFunctionInfo>();
|
||||
|
||||
// Compute the nearest common dominator of all unwind predecessors
|
||||
MachineBasicBlock *Header = nullptr;
|
||||
int MBBNumber = MBB.getNumber();
|
||||
for (auto *Pred : MBB.predecessors()) {
|
||||
if (Pred->getNumber() < MBBNumber) {
|
||||
Header = Header ? MDT.findNearestCommonDominator(Header, Pred) : Pred;
|
||||
assert(!explicitlyBranchesTo(Pred, &MBB) &&
|
||||
"Explicit branch to an EH pad!");
|
||||
}
|
||||
}
|
||||
if (!Header)
|
||||
return;
|
||||
|
||||
assert(&MBB != &MF.front() && "Header blocks shouldn't have predecessors");
|
||||
MachineBasicBlock *LayoutPred = MBB.getPrevNode();
|
||||
|
||||
// If the nearest common dominator is inside a more deeply nested context,
|
||||
// walk out to the nearest scope which isn't more deeply nested.
|
||||
for (MachineFunction::iterator I(LayoutPred), E(Header); I != E; --I) {
|
||||
if (MachineBasicBlock *ScopeTop = ScopeTops[I->getNumber()]) {
|
||||
if (ScopeTop->getNumber() > Header->getNumber()) {
|
||||
// Skip over an intervening scope.
|
||||
I = std::next(ScopeTop->getIterator());
|
||||
} else {
|
||||
// We found a scope level at an appropriate depth.
|
||||
Header = ScopeTop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decide where in Header to put the TRY_TABLE.
|
||||
|
||||
// Instructions that should go before the TRY_TABLE.
|
||||
SmallPtrSet<const MachineInstr *, 4> BeforeSet;
|
||||
// Instructions that should go after the TRY_TABLE.
|
||||
SmallPtrSet<const MachineInstr *, 4> AfterSet;
|
||||
for (const auto &MI : *Header) {
|
||||
// If there is a previously placed LOOP marker and the bottom block of the
|
||||
// loop is above MBB, it should be after the TRY_TABLE, because the loop is
|
||||
// nested in this TRY_TABLE. Otherwise it should be before the TRY_TABLE.
|
||||
if (MI.getOpcode() == WebAssembly::LOOP) {
|
||||
auto *LoopBottom = BeginToEnd[&MI]->getParent()->getPrevNode();
|
||||
if (MBB.getNumber() > LoopBottom->getNumber())
|
||||
AfterSet.insert(&MI);
|
||||
#ifndef NDEBUG
|
||||
else
|
||||
BeforeSet.insert(&MI);
|
||||
#endif
|
||||
}
|
||||
|
||||
// All previously inserted BLOCK/TRY_TABLE markers should be after the
|
||||
// TRY_TABLE because they are all nested blocks/try_tables.
|
||||
if (MI.getOpcode() == WebAssembly::BLOCK ||
|
||||
MI.getOpcode() == WebAssembly::TRY_TABLE)
|
||||
AfterSet.insert(&MI);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// All END_(BLOCK/LOOP/TRY_TABLE) markers should be before the TRY_TABLE.
|
||||
if (MI.getOpcode() == WebAssembly::END_BLOCK ||
|
||||
MI.getOpcode() == WebAssembly::END_LOOP ||
|
||||
MI.getOpcode() == WebAssembly::END_TRY_TABLE)
|
||||
BeforeSet.insert(&MI);
|
||||
#endif
|
||||
|
||||
// Terminators should go after the TRY_TABLE.
|
||||
if (MI.isTerminator())
|
||||
AfterSet.insert(&MI);
|
||||
}
|
||||
|
||||
// If Header unwinds to MBB (= Header contains 'invoke'), the try_table block
|
||||
// should contain the call within it. So the call should go after the
|
||||
// TRY_TABLE. The exception is when the header's terminator is a rethrow
|
||||
// instruction, in which case that instruction, not a call instruction before
|
||||
// it, is gonna throw.
|
||||
MachineInstr *ThrowingCall = nullptr;
|
||||
if (MBB.isPredecessor(Header)) {
|
||||
auto TermPos = Header->getFirstTerminator();
|
||||
if (TermPos == Header->end() ||
|
||||
TermPos->getOpcode() != WebAssembly::RETHROW) {
|
||||
for (auto &MI : reverse(*Header)) {
|
||||
if (MI.isCall()) {
|
||||
AfterSet.insert(&MI);
|
||||
ThrowingCall = &MI;
|
||||
// Possibly throwing calls are usually wrapped by EH_LABEL
|
||||
// instructions. We don't want to split them and the call.
|
||||
if (MI.getIterator() != Header->begin() &&
|
||||
std::prev(MI.getIterator())->isEHLabel()) {
|
||||
AfterSet.insert(&*std::prev(MI.getIterator()));
|
||||
ThrowingCall = &*std::prev(MI.getIterator());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Local expression tree should go after the TRY_TABLE.
|
||||
// For BLOCK placement, we start the search from the previous instruction of a
|
||||
// BB's terminator, but in TRY_TABLE's case, we should start from the previous
|
||||
// instruction of a call that can throw, or a EH_LABEL that precedes the call,
|
||||
// because the return values of the call's previous instructions can be
|
||||
// stackified and consumed by the throwing call.
|
||||
auto SearchStartPt = ThrowingCall ? MachineBasicBlock::iterator(ThrowingCall)
|
||||
: Header->getFirstTerminator();
|
||||
for (auto I = SearchStartPt, E = Header->begin(); I != E; --I) {
|
||||
if (std::prev(I)->isDebugInstr() || std::prev(I)->isPosition())
|
||||
continue;
|
||||
if (WebAssembly::isChild(*std::prev(I), MFI))
|
||||
AfterSet.insert(&*std::prev(I));
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the TRY_TABLE and a BLOCK for the catch destination. We currently
|
||||
// generate only one CATCH clause for a TRY_TABLE, so we need one BLOCK for
|
||||
// its destination.
|
||||
//
|
||||
// Header:
|
||||
// block
|
||||
// try_table (catch ... $MBB)
|
||||
// ...
|
||||
//
|
||||
// MBB:
|
||||
// end_try_table
|
||||
// end_block ;; destination of (catch ...)
|
||||
// ... catch handler body ...
|
||||
auto InsertPos = getLatestInsertPos(Header, BeforeSet, AfterSet);
|
||||
MachineInstrBuilder BlockMIB =
|
||||
BuildMI(*Header, InsertPos, Header->findDebugLoc(InsertPos),
|
||||
TII.get(WebAssembly::BLOCK));
|
||||
auto *Block = BlockMIB.getInstr();
|
||||
MachineInstrBuilder TryTableMIB =
|
||||
BuildMI(*Header, InsertPos, Header->findDebugLoc(InsertPos),
|
||||
TII.get(WebAssembly::TRY_TABLE))
|
||||
.addImm(int64_t(WebAssembly::BlockType::Void))
|
||||
.addImm(1); // # of catch clauses
|
||||
auto *TryTable = TryTableMIB.getInstr();
|
||||
|
||||
// Add a CATCH_*** clause to the TRY_TABLE. These are pseudo instructions
|
||||
// following the destination END_BLOCK to simulate block return values,
|
||||
// because we currently don't support them.
|
||||
auto *Catch = WebAssembly::findCatch(&MBB);
|
||||
switch (Catch->getOpcode()) {
|
||||
case WebAssembly::CATCH:
|
||||
// CATCH's destination block's return type is the extracted value type,
|
||||
// which is currently i32 for all supported tags.
|
||||
BlockMIB.addImm(int64_t(WebAssembly::BlockType::I32));
|
||||
TryTableMIB.addImm(wasm::WASM_OPCODE_CATCH);
|
||||
for (const auto &Use : Catch->uses()) {
|
||||
// The only use operand a CATCH can have is the tag symbol.
|
||||
TryTableMIB.addExternalSymbol(Use.getSymbolName());
|
||||
break;
|
||||
}
|
||||
TryTableMIB.addMBB(&MBB);
|
||||
break;
|
||||
case WebAssembly::CATCH_REF:
|
||||
// CATCH_REF's destination block's return type is the extracted value type
|
||||
// followed by an exnref, which is (i32, exnref) in our case. We assign the
|
||||
// actual multiavlue signature in MCInstLower. MO_CATCH_BLOCK_SIG signals
|
||||
// that this operand is used for catch_ref's multivalue destination.
|
||||
BlockMIB.addImm(int64_t(WebAssembly::BlockType::Multivalue));
|
||||
Block->getOperand(0).setTargetFlags(WebAssemblyII::MO_CATCH_BLOCK_SIG);
|
||||
TryTableMIB.addImm(wasm::WASM_OPCODE_CATCH_REF);
|
||||
for (const auto &Use : Catch->uses()) {
|
||||
TryTableMIB.addExternalSymbol(Use.getSymbolName());
|
||||
break;
|
||||
}
|
||||
TryTableMIB.addMBB(&MBB);
|
||||
break;
|
||||
case WebAssembly::CATCH_ALL:
|
||||
// CATCH_ALL's destination block's return type is void.
|
||||
BlockMIB.addImm(int64_t(WebAssembly::BlockType::Void));
|
||||
TryTableMIB.addImm(wasm::WASM_OPCODE_CATCH_ALL);
|
||||
TryTableMIB.addMBB(&MBB);
|
||||
break;
|
||||
case WebAssembly::CATCH_ALL_REF:
|
||||
// CATCH_ALL_REF's destination block's return type is exnref.
|
||||
BlockMIB.addImm(int64_t(WebAssembly::BlockType::Exnref));
|
||||
TryTableMIB.addImm(wasm::WASM_OPCODE_CATCH_ALL_REF);
|
||||
TryTableMIB.addMBB(&MBB);
|
||||
break;
|
||||
}
|
||||
|
||||
// Decide where in MBB to put the END_TRY_TABLE, and the END_BLOCK for the
|
||||
// CATCH destination.
|
||||
BeforeSet.clear();
|
||||
AfterSet.clear();
|
||||
for (const auto &MI : MBB) {
|
||||
#ifndef NDEBUG
|
||||
// END_TRY_TABLE should precede existing LOOP markers.
|
||||
if (MI.getOpcode() == WebAssembly::LOOP)
|
||||
AfterSet.insert(&MI);
|
||||
#endif
|
||||
|
||||
// If there is a previously placed END_LOOP marker and the header of the
|
||||
// loop is above this try_table's header, the END_LOOP should be placed
|
||||
// after the END_TRY_TABLE, because the loop contains this block. Otherwise
|
||||
// the END_LOOP should be placed before the END_TRY_TABLE.
|
||||
if (MI.getOpcode() == WebAssembly::END_LOOP) {
|
||||
if (EndToBegin[&MI]->getParent()->getNumber() >= Header->getNumber())
|
||||
BeforeSet.insert(&MI);
|
||||
#ifndef NDEBUG
|
||||
else
|
||||
AfterSet.insert(&MI);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// CATCH, CATCH_REF, CATCH_ALL, and CATCH_ALL_REF are pseudo-instructions
|
||||
// that simulate the block return value, so they should be placed after the
|
||||
// END_TRY_TABLE.
|
||||
if (WebAssembly::isCatch(MI.getOpcode()))
|
||||
AfterSet.insert(&MI);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Mark the end of the TRY_TABLE and the BLOCK.
|
||||
InsertPos = getEarliestInsertPos(&MBB, BeforeSet, AfterSet);
|
||||
MachineInstr *EndTryTable =
|
||||
BuildMI(MBB, InsertPos, MBB.findPrevDebugLoc(InsertPos),
|
||||
TII.get(WebAssembly::END_TRY_TABLE));
|
||||
registerTryScope(TryTable, EndTryTable, &MBB);
|
||||
MachineInstr *EndBlock =
|
||||
BuildMI(MBB, InsertPos, MBB.findPrevDebugLoc(InsertPos),
|
||||
TII.get(WebAssembly::END_BLOCK));
|
||||
registerScope(Block, EndBlock);
|
||||
// Track the farthest-spanning scope that ends at this point.
|
||||
updateScopeTops(Header, &MBB);
|
||||
}
|
||||
|
||||
void WebAssemblyCFGStackify::removeUnnecessaryInstrs(MachineFunction &MF) {
|
||||
if (WebAssembly::WasmEnableExnref)
|
||||
return;
|
||||
|
||||
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
|
||||
|
||||
// When there is an unconditional branch right before a catch instruction and
|
||||
@@ -1445,6 +1700,7 @@ void WebAssemblyCFGStackify::recalculateScopeTops(MachineFunction &MF) {
|
||||
case WebAssembly::END_BLOCK:
|
||||
case WebAssembly::END_LOOP:
|
||||
case WebAssembly::END_TRY:
|
||||
case WebAssembly::END_TRY_TABLE:
|
||||
case WebAssembly::DELEGATE:
|
||||
updateScopeTops(EndToBegin[&MI]->getParent(), &MBB);
|
||||
break;
|
||||
@@ -1502,6 +1758,7 @@ void WebAssemblyCFGStackify::fixEndsAtEndOfFunction(MachineFunction &MF) {
|
||||
}
|
||||
case WebAssembly::END_BLOCK:
|
||||
case WebAssembly::END_LOOP:
|
||||
case WebAssembly::END_TRY_TABLE:
|
||||
case WebAssembly::DELEGATE:
|
||||
EndToBegin[&MI]->getOperand(0).setImm(int32_t(RetType));
|
||||
continue;
|
||||
@@ -1528,7 +1785,7 @@ static void appendEndToFunction(MachineFunction &MF,
|
||||
TII.get(WebAssembly::END_FUNCTION));
|
||||
}
|
||||
|
||||
/// Insert BLOCK/LOOP/TRY markers at appropriate places.
|
||||
/// Insert BLOCK/LOOP/TRY/TRY_TABLE markers at appropriate places.
|
||||
void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
|
||||
// We allocate one more than the number of blocks in the function to
|
||||
// accommodate for the possible fake block we may insert at the end.
|
||||
@@ -1540,15 +1797,25 @@ void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
|
||||
const MCAsmInfo *MCAI = MF.getTarget().getMCAsmInfo();
|
||||
for (auto &MBB : MF) {
|
||||
if (MBB.isEHPad()) {
|
||||
// Place the TRY for MBB if MBB is the EH pad of an exception.
|
||||
// Place the TRY/TRY_TABLE for MBB if MBB is the EH pad of an exception.
|
||||
if (MCAI->getExceptionHandlingType() == ExceptionHandling::Wasm &&
|
||||
MF.getFunction().hasPersonalityFn())
|
||||
placeTryMarker(MBB);
|
||||
MF.getFunction().hasPersonalityFn()) {
|
||||
if (WebAssembly::WasmEnableExnref)
|
||||
placeTryTableMarker(MBB);
|
||||
else
|
||||
placeTryMarker(MBB);
|
||||
}
|
||||
} else {
|
||||
// Place the BLOCK for MBB if MBB is branched to from above.
|
||||
placeBlockMarker(MBB);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME We return here temporarily until we implement fixing unwind
|
||||
// mismatches for the new exnref proposal.
|
||||
if (WebAssembly::WasmEnableExnref)
|
||||
return;
|
||||
|
||||
// Fix mismatches in unwind destinations induced by linearizing the code.
|
||||
if (MCAI->getExceptionHandlingType() == ExceptionHandling::Wasm &&
|
||||
MF.getFunction().hasPersonalityFn()) {
|
||||
@@ -1668,11 +1935,14 @@ void WebAssemblyCFGStackify::rewriteDepthImmediates(MachineFunction &MF) {
|
||||
for (auto &MBB : reverse(MF)) {
|
||||
for (MachineInstr &MI : llvm::reverse(MBB)) {
|
||||
switch (MI.getOpcode()) {
|
||||
case WebAssembly::TRY_TABLE:
|
||||
RewriteOperands(MI);
|
||||
[[fallthrough]];
|
||||
case WebAssembly::BLOCK:
|
||||
case WebAssembly::TRY:
|
||||
assert(ScopeTops[Stack.back().first->getNumber()]->getNumber() <=
|
||||
MBB.getNumber() &&
|
||||
"Block/try marker should be balanced");
|
||||
"Block/try/try_table marker should be balanced");
|
||||
Stack.pop_back();
|
||||
break;
|
||||
|
||||
@@ -1687,6 +1957,7 @@ void WebAssemblyCFGStackify::rewriteDepthImmediates(MachineFunction &MF) {
|
||||
[[fallthrough]];
|
||||
}
|
||||
case WebAssembly::END_BLOCK:
|
||||
case WebAssembly::END_TRY_TABLE:
|
||||
Stack.push_back(std::make_pair(&MBB, &MI));
|
||||
break;
|
||||
|
||||
@@ -1744,7 +2015,8 @@ bool WebAssemblyCFGStackify::runOnMachineFunction(MachineFunction &MF) {
|
||||
// Liveness is not tracked for VALUE_STACK physreg.
|
||||
MF.getRegInfo().invalidateLiveness();
|
||||
|
||||
// Place the BLOCK/LOOP/TRY markers to indicate the beginnings of scopes.
|
||||
// Place the BLOCK/LOOP/TRY/TRY_TABLE markers to indicate the beginnings of
|
||||
// scopes.
|
||||
placeMarkers(MF);
|
||||
|
||||
// Remove unnecessary instructions possibly introduced by try/end_trys.
|
||||
@@ -1755,8 +2027,8 @@ bool WebAssemblyCFGStackify::runOnMachineFunction(MachineFunction &MF) {
|
||||
// Convert MBB operands in terminators to relative depth immediates.
|
||||
rewriteDepthImmediates(MF);
|
||||
|
||||
// Fix up block/loop/try signatures at the end of the function to conform to
|
||||
// WebAssembly's rules.
|
||||
// Fix up block/loop/try/try_table signatures at the end of the function to
|
||||
// conform to WebAssembly's rules.
|
||||
fixEndsAtEndOfFunction(MF);
|
||||
|
||||
// Add an end instruction at the end of the function body.
|
||||
|
||||
@@ -211,8 +211,11 @@ void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
|
||||
case Intrinsic::wasm_catch: {
|
||||
int Tag = Node->getConstantOperandVal(2);
|
||||
SDValue SymNode = getTagSymNode(Tag, CurDAG);
|
||||
unsigned CatchOpcode = WebAssembly::WasmEnableExnref
|
||||
? WebAssembly::CATCH
|
||||
: WebAssembly::CATCH_LEGACY;
|
||||
MachineSDNode *Catch =
|
||||
CurDAG->getMachineNode(WebAssembly::CATCH_LEGACY, DL,
|
||||
CurDAG->getMachineNode(CatchOpcode, DL,
|
||||
{
|
||||
PtrVT, // exception pointer
|
||||
MVT::Other // outchain type
|
||||
|
||||
@@ -125,15 +125,46 @@ defm DEBUG_UNREACHABLE : NRI<(outs), (ins), [(debugtrap)], "unreachable", 0x00>;
|
||||
// Exception handling instructions
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// A list of catch clauses attached to try_table.
|
||||
def CatchListAsmOperand : AsmOperandClass { let Name = "CatchList"; }
|
||||
let OperandNamespace = "WebAssembly", OperandType = "OPERAND_CATCH_LIST" in
|
||||
def catch_list : Operand<i32> {
|
||||
let ParserMatchClass = CatchListAsmOperand;
|
||||
let PrintMethod = "printCatchList";
|
||||
}
|
||||
|
||||
let Predicates = [HasExceptionHandling] in {
|
||||
|
||||
// Throwing an exception: throw
|
||||
// Throwing an exception: throw / throw_ref
|
||||
let isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in {
|
||||
defm THROW : I<(outs), (ins tag_op:$tag, variable_ops),
|
||||
(outs), (ins tag_op:$tag), [],
|
||||
"throw \t$tag", "throw \t$tag", 0x08>;
|
||||
defm THROW_REF : I<(outs), (ins EXNREF:$exn), (outs), (ins), [],
|
||||
"throw_ref \t$exn", "throw_ref", 0x0a>;
|
||||
} // isTerminator = 1, hasCtrlDep = 1, isBarrier = 1
|
||||
|
||||
// Region within which an exception is caught: try / end_try
|
||||
let Uses = [VALUE_STACK], Defs = [VALUE_STACK] in {
|
||||
defm TRY_TABLE : I<(outs), (ins Signature:$sig, variable_ops),
|
||||
(outs), (ins Signature:$sig, catch_list:$cal), [],
|
||||
"try_table \t$sig", "try_table \t$sig $cal", 0x1f>;
|
||||
defm END_TRY_TABLE : NRI<(outs), (ins), [], "end_try_table", 0x0b>;
|
||||
} // Uses = [VALUE_STACK], Defs = [VALUE_STACK]
|
||||
|
||||
// Pseudo instructions that represent catch / catch_ref / catch_all /
|
||||
// catch_all_ref clauses in a try_table instruction.
|
||||
let hasCtrlDep = 1, hasSideEffects = 1, isCodeGenOnly = 1 in {
|
||||
let variadicOpsAreDefs = 1 in {
|
||||
defm CATCH : I<(outs), (ins tag_op:$tag, variable_ops),
|
||||
(outs), (ins tag_op:$tag), []>;
|
||||
defm CATCH_REF : I<(outs), (ins tag_op:$tag, variable_ops),
|
||||
(outs), (ins tag_op:$tag), []>;
|
||||
}
|
||||
defm CATCH_ALL : NRI<(outs), (ins), []>;
|
||||
defm CATCH_ALL_REF : I<(outs EXNREF:$dst), (ins), (outs), (ins), []>;
|
||||
}
|
||||
|
||||
// Pseudo instructions: cleanupret / catchret
|
||||
let isTerminator = 1, hasSideEffects = 1, isBarrier = 1, hasCtrlDep = 1,
|
||||
isPseudo = 1, isEHScopeReturn = 1 in {
|
||||
@@ -147,9 +178,10 @@ let isTerminator = 1, hasSideEffects = 1, isBarrier = 1, hasCtrlDep = 1,
|
||||
// usage gets low enough.
|
||||
|
||||
// Rethrowing an exception: rethrow
|
||||
// The new exnref proposal also uses this instruction as an interim pseudo
|
||||
// instruction before we convert it to a THROW_REF.
|
||||
let isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in
|
||||
defm RETHROW : NRI<(outs), (ins i32imm:$depth), [], "rethrow \t$depth", 0x09>;
|
||||
// isTerminator = 1, hasCtrlDep = 1, isBarrier = 1
|
||||
// The depth argument will be computed in CFGStackify. We set it to 0 here for
|
||||
// now.
|
||||
def : Pat<(int_wasm_rethrow), (RETHROW 0)>;
|
||||
|
||||
@@ -37,6 +37,7 @@ class WebAssemblyLateEHPrepare final : public MachineFunctionPass {
|
||||
void recordCatchRetBBs(MachineFunction &MF);
|
||||
bool hoistCatches(MachineFunction &MF);
|
||||
bool addCatchAlls(MachineFunction &MF);
|
||||
bool addCatchRefsAndThrowRefs(MachineFunction &MF);
|
||||
bool replaceFuncletReturns(MachineFunction &MF);
|
||||
bool removeUnnecessaryUnreachables(MachineFunction &MF);
|
||||
bool restoreStackPointer(MachineFunction &MF);
|
||||
@@ -127,6 +128,8 @@ bool WebAssemblyLateEHPrepare::runOnMachineFunction(MachineFunction &MF) {
|
||||
Changed |= hoistCatches(MF);
|
||||
Changed |= addCatchAlls(MF);
|
||||
Changed |= replaceFuncletReturns(MF);
|
||||
if (WebAssembly::WasmEnableExnref)
|
||||
Changed |= addCatchRefsAndThrowRefs(MF);
|
||||
}
|
||||
Changed |= removeUnnecessaryUnreachables(MF);
|
||||
if (MF.getFunction().hasPersonalityFn())
|
||||
@@ -214,9 +217,12 @@ bool WebAssemblyLateEHPrepare::addCatchAlls(MachineFunction &MF) {
|
||||
if (InsertPos == MBB.end() ||
|
||||
!WebAssembly::isCatch(InsertPos->getOpcode())) {
|
||||
Changed = true;
|
||||
unsigned CatchAllOpcode = WebAssembly::WasmEnableExnref
|
||||
? WebAssembly::CATCH_ALL
|
||||
: WebAssembly::CATCH_ALL_LEGACY;
|
||||
BuildMI(MBB, InsertPos,
|
||||
InsertPos == MBB.end() ? DebugLoc() : InsertPos->getDebugLoc(),
|
||||
TII.get(WebAssembly::CATCH_ALL_LEGACY));
|
||||
TII.get(CatchAllOpcode));
|
||||
}
|
||||
}
|
||||
return Changed;
|
||||
@@ -248,6 +254,10 @@ bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
|
||||
case WebAssembly::CLEANUPRET: {
|
||||
// Replace a cleanupret with a rethrow. For C++ support, currently
|
||||
// rethrow's immediate argument is always 0 (= the latest exception).
|
||||
//
|
||||
// Even when -wasm-enable-exnref is true, we use a RETHROW here for the
|
||||
// moment. This will be converted to a THROW_REF in
|
||||
// addCatchRefsAndThrowRefs.
|
||||
BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::RETHROW))
|
||||
.addImm(0);
|
||||
TI->eraseFromParent();
|
||||
@@ -259,14 +269,74 @@ bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
|
||||
return Changed;
|
||||
}
|
||||
|
||||
// Remove unnecessary unreachables after a throw or rethrow.
|
||||
// Add CATCH_REF and CATCH_ALL_REF pseudo instructions to EH pads, and convert
|
||||
// RETHROWs to THROW_REFs.
|
||||
bool WebAssemblyLateEHPrepare::addCatchRefsAndThrowRefs(MachineFunction &MF) {
|
||||
bool Changed = false;
|
||||
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
|
||||
auto &MRI = MF.getRegInfo();
|
||||
DenseMap<MachineBasicBlock *, SmallVector<MachineInstr *, 2>> EHPadToRethrows;
|
||||
|
||||
// Create a map of <EH pad, a vector of RETHROWs rethrowing its exception>
|
||||
for (auto &MBB : MF) {
|
||||
for (auto &MI : MBB) {
|
||||
if (MI.getOpcode() == WebAssembly::RETHROW) {
|
||||
Changed = true;
|
||||
auto *EHPad = getMatchingEHPad(&MI);
|
||||
EHPadToRethrows[EHPad].push_back(&MI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert CATCH into CATCH_REF and CATCH_ALL into CATCH_ALL_REF, when the
|
||||
// caught exception is rethrown. And convert RETHROWs to THROW_REFs.
|
||||
for (auto &[EHPad, Rethrows] : EHPadToRethrows) {
|
||||
auto *Catch = WebAssembly::findCatch(EHPad);
|
||||
auto *InsertPos = Catch->getIterator()->getNextNode();
|
||||
auto ExnReg = MRI.createVirtualRegister(&WebAssembly::EXNREFRegClass);
|
||||
if (Catch->getOpcode() == WebAssembly::CATCH) {
|
||||
MachineInstrBuilder MIB = BuildMI(*EHPad, InsertPos, Catch->getDebugLoc(),
|
||||
TII.get(WebAssembly::CATCH_REF));
|
||||
// Copy defs (= extracted values) from the old CATCH to the new CATCH_REF
|
||||
for (const auto &Def : Catch->defs())
|
||||
MIB.addDef(Def.getReg());
|
||||
MIB.addDef(ExnReg); // Attach the exnref def after extracted values
|
||||
// Copy the tag symbol (The only use operand a CATCH can have is the tag
|
||||
// symbol)
|
||||
for (const auto &Use : Catch->uses()) {
|
||||
MIB.addExternalSymbol(Use.getSymbolName());
|
||||
break;
|
||||
}
|
||||
} else if (Catch->getOpcode() == WebAssembly::CATCH_ALL) {
|
||||
BuildMI(*EHPad, InsertPos, Catch->getDebugLoc(),
|
||||
TII.get(WebAssembly::CATCH_ALL_REF))
|
||||
.addDef(ExnReg);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
Catch->eraseFromParent();
|
||||
|
||||
for (auto *Rethrow : Rethrows) {
|
||||
auto InsertPos = std::next(Rethrow->getIterator());
|
||||
BuildMI(*Rethrow->getParent(), InsertPos, Rethrow->getDebugLoc(),
|
||||
TII.get(WebAssembly::THROW_REF))
|
||||
.addReg(ExnReg);
|
||||
Rethrow->eraseFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
return Changed;
|
||||
}
|
||||
|
||||
// Remove unnecessary unreachables after a throw/rethrow/throw_ref.
|
||||
bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables(
|
||||
MachineFunction &MF) {
|
||||
bool Changed = false;
|
||||
for (auto &MBB : MF) {
|
||||
for (auto &MI : MBB) {
|
||||
if (MI.getOpcode() != WebAssembly::THROW &&
|
||||
MI.getOpcode() != WebAssembly::RETHROW)
|
||||
MI.getOpcode() != WebAssembly::RETHROW &&
|
||||
MI.getOpcode() != WebAssembly::THROW_REF)
|
||||
continue;
|
||||
Changed = true;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "WebAssemblyMCInstLower.h"
|
||||
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
|
||||
#include "TargetInfo/WebAssemblyTargetInfo.h"
|
||||
#include "Utils/WebAssemblyTypeUtilities.h"
|
||||
#include "WebAssemblyAsmPrinter.h"
|
||||
@@ -220,12 +221,27 @@ void WebAssemblyMCInstLower::lower(const MachineInstr *MI,
|
||||
|
||||
MCOp = lowerTypeIndexOperand(std::move(Returns), std::move(Params));
|
||||
break;
|
||||
} else if (Info.OperandType == WebAssembly::OPERAND_SIGNATURE) {
|
||||
}
|
||||
if (Info.OperandType == WebAssembly::OPERAND_SIGNATURE) {
|
||||
auto BT = static_cast<WebAssembly::BlockType>(MO.getImm());
|
||||
assert(BT != WebAssembly::BlockType::Invalid);
|
||||
if (BT == WebAssembly::BlockType::Multivalue) {
|
||||
SmallVector<wasm::ValType, 1> Returns;
|
||||
getFunctionReturns(MI, Returns);
|
||||
SmallVector<wasm::ValType, 2> Returns;
|
||||
// Multivalue blocks are emitted in two cases:
|
||||
// 1. When the blocks will never be exited and are at the ends of
|
||||
// functions (see
|
||||
// WebAssemblyCFGStackify::fixEndsAtEndOfFunction). In this case
|
||||
// the exact multivalue signature can always be inferred from the
|
||||
// return type of the parent function.
|
||||
// 2. (catch_ref ...) clause in try_table instruction. Currently all
|
||||
// tags we support (cpp_exception and c_longjmp) throws a single
|
||||
// i32, so the multivalue signature for this case will be (i32,
|
||||
// exnref). Having MO_CATCH_BLOCK_SIG target flags means this is
|
||||
// a destination of a catch_ref.
|
||||
if (MO.getTargetFlags() == WebAssemblyII::MO_CATCH_BLOCK_SIG)
|
||||
Returns = {wasm::ValType::I32, wasm::ValType::EXNREF};
|
||||
else
|
||||
getFunctionReturns(MI, Returns);
|
||||
MCOp = lowerTypeIndexOperand(std::move(Returns),
|
||||
SmallVector<wasm::ValType, 4>());
|
||||
break;
|
||||
|
||||
@@ -43,6 +43,8 @@ bool WebAssembly::mayThrow(const MachineInstr &MI) {
|
||||
switch (MI.getOpcode()) {
|
||||
case WebAssembly::THROW:
|
||||
case WebAssembly::THROW_S:
|
||||
case WebAssembly::THROW_REF:
|
||||
case WebAssembly::THROW_REF_S:
|
||||
case WebAssembly::RETHROW:
|
||||
case WebAssembly::RETHROW_S:
|
||||
return true;
|
||||
|
||||
@@ -109,7 +109,7 @@ ehcleanup: ; preds = %entry
|
||||
}
|
||||
|
||||
; Calling a function that may throw within a 'catch (...)' generates a
|
||||
; temrinatepad, because __cxa_end_catch() also can throw within 'catch (...)'.
|
||||
; terminatepad, because __cxa_end_catch() also can throw within 'catch (...)'.
|
||||
;
|
||||
; void foo();
|
||||
; void terminatepad() {
|
||||
|
||||
470
llvm/test/CodeGen/WebAssembly/exception.ll
Normal file
470
llvm/test/CodeGen/WebAssembly/exception.ll
Normal file
@@ -0,0 +1,470 @@
|
||||
; RUN: llc < %s -asm-verbose=false -wasm-enable-eh -exception-model=wasm -mattr=+exception-handling -wasm-enable-exnref -verify-machineinstrs | FileCheck --implicit-check-not=ehgcr -allow-deprecated-dag-overlap %s
|
||||
; RUN: llc < %s -asm-verbose=false -wasm-enable-eh -exception-model=wasm -mattr=+exception-handling -wasm-enable-exnref -verify-machineinstrs -O0
|
||||
; RUN: llc < %s -wasm-enable-eh -exception-model=wasm -mattr=+exception-handling -wasm-enable-exnref
|
||||
|
||||
target triple = "wasm32-unknown-unknown"
|
||||
|
||||
%struct.Temp = type { i8 }
|
||||
|
||||
@_ZTIi = external dso_local constant ptr
|
||||
|
||||
; CHECK: .tagtype __cpp_exception i32
|
||||
|
||||
; CHECK-LABEL: throw:
|
||||
; CHECK: throw __cpp_exception
|
||||
; CHECK-NOT: unreachable
|
||||
define void @throw(ptr %p) {
|
||||
call void @llvm.wasm.throw(i32 0, ptr %p)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Simple test with a try-catch
|
||||
;
|
||||
; void foo();
|
||||
; void catch() {
|
||||
; try {
|
||||
; foo();
|
||||
; } catch (int) {
|
||||
; }
|
||||
; }
|
||||
|
||||
; CHECK-LABEL: catch:
|
||||
; CHECK: global.get __stack_pointer
|
||||
; CHECK: local.set 0
|
||||
; CHECK: block
|
||||
; CHECK: block () -> (i32, exnref)
|
||||
; CHECK: try_table (catch_ref __cpp_exception 0)
|
||||
; CHECK: call foo
|
||||
; CHECK: br 2
|
||||
; CHECK: end_try_table
|
||||
; CHECK: end_block
|
||||
; CHECK: local.set 2
|
||||
; CHECK: local.get 0
|
||||
; CHECK: global.set __stack_pointer
|
||||
; CHECK: i32.store __wasm_lpad_context
|
||||
; CHECK: call _Unwind_CallPersonality
|
||||
; CHECK: block
|
||||
; CHECK: br_if 0
|
||||
; CHECK: call __cxa_begin_catch
|
||||
; CHECK: call __cxa_end_catch
|
||||
; CHECK: br 1
|
||||
; CHECK: end_block
|
||||
; CHECK: local.get 2
|
||||
; CHECK: throw_ref
|
||||
; CHECK: end_block
|
||||
define void @catch() personality ptr @__gxx_wasm_personality_v0 {
|
||||
entry:
|
||||
invoke void @foo()
|
||||
to label %try.cont unwind label %catch.dispatch
|
||||
|
||||
catch.dispatch: ; preds = %entry
|
||||
%0 = catchswitch within none [label %catch.start] unwind to caller
|
||||
|
||||
catch.start: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [ptr @_ZTIi]
|
||||
%2 = call ptr @llvm.wasm.get.exception(token %1)
|
||||
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
|
||||
%4 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi)
|
||||
%matches = icmp eq i32 %3, %4
|
||||
br i1 %matches, label %catch, label %rethrow
|
||||
|
||||
catch: ; preds = %catch.start
|
||||
%5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
|
||||
call void @__cxa_end_catch() [ "funclet"(token %1) ]
|
||||
catchret from %1 to label %try.cont
|
||||
|
||||
rethrow: ; preds = %catch.start
|
||||
call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
|
||||
unreachable
|
||||
|
||||
try.cont: ; preds = %catch, %entry
|
||||
ret void
|
||||
}
|
||||
|
||||
; Destructor (cleanup) test
|
||||
;
|
||||
; void foo();
|
||||
; struct Temp {
|
||||
; ~Temp() {}
|
||||
; };
|
||||
; void cleanup() {
|
||||
; Temp t;
|
||||
; foo();
|
||||
; }
|
||||
|
||||
; CHECK-LABEL: cleanup:
|
||||
; CHECK: block
|
||||
; CHECK: block exnref
|
||||
; CHECK: try_table (catch_all_ref 0)
|
||||
; CHECK: call foo
|
||||
; CHECK: br 2
|
||||
; CHECK: end_try_table
|
||||
; CHECK: end_block
|
||||
; CHECK: local.set 1
|
||||
; CHECK: global.set __stack_pointer
|
||||
; CHECK: call _ZN4TempD2Ev
|
||||
; CHECK: local.get 1
|
||||
; CHECK: throw_ref
|
||||
; CHECK: end_block
|
||||
; CHECK: call _ZN4TempD2Ev
|
||||
define void @cleanup() personality ptr @__gxx_wasm_personality_v0 {
|
||||
entry:
|
||||
%t = alloca %struct.Temp, align 1
|
||||
invoke void @foo()
|
||||
to label %invoke.cont unwind label %ehcleanup
|
||||
|
||||
invoke.cont: ; preds = %entry
|
||||
%call = call ptr @_ZN4TempD2Ev(ptr %t)
|
||||
ret void
|
||||
|
||||
ehcleanup: ; preds = %entry
|
||||
%0 = cleanuppad within none []
|
||||
%call1 = call ptr @_ZN4TempD2Ev(ptr %t) [ "funclet"(token %0) ]
|
||||
cleanupret from %0 unwind to caller
|
||||
}
|
||||
|
||||
; Calling a function that may throw within a 'catch (...)' generates a
|
||||
; terminatepad, because __cxa_end_catch() also can throw within 'catch (...)'.
|
||||
;
|
||||
; void foo();
|
||||
; void terminatepad() {
|
||||
; try {
|
||||
; foo();
|
||||
; } catch (...) {
|
||||
; foo();
|
||||
; }
|
||||
; }
|
||||
|
||||
; CHECK-LABEL: terminatepad
|
||||
; CHECK: block
|
||||
; CHECK: block i32
|
||||
; CHECK: try_table (catch __cpp_exception 0)
|
||||
; CHECK: call foo
|
||||
; CHECK: br 2
|
||||
; CHECK: end_try_table
|
||||
; CHECK: end_block
|
||||
; CHECK: call __cxa_begin_catch
|
||||
; CHECK: block
|
||||
; CHECK: block exnref
|
||||
; CHECK: try_table (catch_all_ref 0)
|
||||
; CHECK: call foo
|
||||
; CHECK: br 2
|
||||
; CHECK: end_try_table
|
||||
; CHECK: end_block
|
||||
; CHECK: local.set 2
|
||||
; CHECK: block
|
||||
; CHECK: block
|
||||
; CHECK: try_table (catch_all 0)
|
||||
; CHECK: call __cxa_end_catch
|
||||
; CHECK: br 2
|
||||
; CHECK: end_try_table
|
||||
; CHECK: end_block
|
||||
; CHECK: call _ZSt9terminatev
|
||||
; CHECK: unreachable
|
||||
; CHECK: end_block
|
||||
; CHECK: local.get 2
|
||||
; CHECK: throw_ref
|
||||
; CHECK: end_block
|
||||
; CHECK: call __cxa_end_catch
|
||||
; CHECK: end_block
|
||||
define void @terminatepad() personality ptr @__gxx_wasm_personality_v0 {
|
||||
entry:
|
||||
invoke void @foo()
|
||||
to label %try.cont unwind label %catch.dispatch
|
||||
|
||||
catch.dispatch: ; preds = %entry
|
||||
%0 = catchswitch within none [label %catch.start] unwind to caller
|
||||
|
||||
catch.start: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [ptr null]
|
||||
%2 = call ptr @llvm.wasm.get.exception(token %1)
|
||||
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
|
||||
%4 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
|
||||
invoke void @foo() [ "funclet"(token %1) ]
|
||||
to label %invoke.cont1 unwind label %ehcleanup
|
||||
|
||||
invoke.cont1: ; preds = %catch.start
|
||||
call void @__cxa_end_catch() [ "funclet"(token %1) ]
|
||||
catchret from %1 to label %try.cont
|
||||
|
||||
try.cont: ; preds = %invoke.cont1, %entry
|
||||
ret void
|
||||
|
||||
ehcleanup: ; preds = %catch.start
|
||||
%5 = cleanuppad within %1 []
|
||||
invoke void @__cxa_end_catch() [ "funclet"(token %5) ]
|
||||
to label %invoke.cont2 unwind label %terminate
|
||||
|
||||
invoke.cont2: ; preds = %ehcleanup
|
||||
cleanupret from %5 unwind to caller
|
||||
|
||||
terminate: ; preds = %ehcleanup
|
||||
%6 = cleanuppad within %5 []
|
||||
call void @_ZSt9terminatev() [ "funclet"(token %6) ]
|
||||
unreachable
|
||||
}
|
||||
|
||||
; Tests prologues and epilogues are not generated within EH scopes.
|
||||
; They should not be treated as funclets; BBs starting with a catch instruction
|
||||
; should not have a prologue, and BBs ending with a catchret/cleanupret should
|
||||
; not have an epilogue. This is separate from __stack_pointer restoring
|
||||
; instructions after a catch instruction.
|
||||
;
|
||||
; void bar(int) noexcept;
|
||||
; void no_prolog_epilog_in_ehpad() {
|
||||
; int stack_var = 0;
|
||||
; bar(stack_var);
|
||||
; try {
|
||||
; foo();
|
||||
; } catch (int) {
|
||||
; foo();
|
||||
; }
|
||||
; }
|
||||
|
||||
; CHECK-LABEL: no_prolog_epilog_in_ehpad
|
||||
; CHECK: call bar
|
||||
; CHECK: block
|
||||
; CHECK: block () -> (i32, exnref)
|
||||
; CHECK: try_table (catch_ref __cpp_exception 0)
|
||||
; CHECK: call foo
|
||||
; CHECK: br 2
|
||||
; CHECK: end_try_table
|
||||
; CHECK: end_block
|
||||
; CHECK: local.set 2
|
||||
; CHECK-NOT: global.get __stack_pointer
|
||||
; CHECK: global.set __stack_pointer
|
||||
; CHECK: block
|
||||
; CHECK: block
|
||||
; CHECK: br_if 0
|
||||
; CHECK: call __cxa_begin_catch
|
||||
; CHECK: block exnref
|
||||
; CHECK: try_table (catch_all_ref 0)
|
||||
; CHECK: call foo
|
||||
; CHECK: br 3
|
||||
; CHECK: end_try_table
|
||||
; CHECK: end_block
|
||||
; CHECK: local.set 2
|
||||
; CHECK-NOT: global.get __stack_pointer
|
||||
; CHECK: global.set __stack_pointer
|
||||
; CHECK: call __cxa_end_catch
|
||||
; CHECK: local.get 2
|
||||
; CHECK: throw_ref
|
||||
; CHECK-NOT: global.set __stack_pointer
|
||||
; CHECK: end_block
|
||||
; CHECK: local.get 2
|
||||
; CHECK: throw_ref
|
||||
; CHECK: end_block
|
||||
; CHECK-NOT: global.set __stack_pointer
|
||||
; CHECK: call __cxa_end_catch
|
||||
; CHECK: end_block
|
||||
define void @no_prolog_epilog_in_ehpad() personality ptr @__gxx_wasm_personality_v0 {
|
||||
entry:
|
||||
%stack_var = alloca i32, align 4
|
||||
call void @bar(ptr %stack_var)
|
||||
invoke void @foo()
|
||||
to label %try.cont unwind label %catch.dispatch
|
||||
|
||||
catch.dispatch: ; preds = %entry
|
||||
%0 = catchswitch within none [label %catch.start] unwind to caller
|
||||
|
||||
catch.start: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [ptr @_ZTIi]
|
||||
%2 = call ptr @llvm.wasm.get.exception(token %1)
|
||||
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
|
||||
%4 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi)
|
||||
%matches = icmp eq i32 %3, %4
|
||||
br i1 %matches, label %catch, label %rethrow
|
||||
|
||||
catch: ; preds = %catch.start
|
||||
%5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
|
||||
%6 = load float, ptr %5, align 4
|
||||
invoke void @foo() [ "funclet"(token %1) ]
|
||||
to label %invoke.cont1 unwind label %ehcleanup
|
||||
|
||||
invoke.cont1: ; preds = %catch
|
||||
call void @__cxa_end_catch() [ "funclet"(token %1) ]
|
||||
catchret from %1 to label %try.cont
|
||||
|
||||
rethrow: ; preds = %catch.start
|
||||
call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
|
||||
unreachable
|
||||
|
||||
try.cont: ; preds = %invoke.cont1, %entry
|
||||
ret void
|
||||
|
||||
ehcleanup: ; preds = %catch
|
||||
%7 = cleanuppad within %1 []
|
||||
call void @__cxa_end_catch() [ "funclet"(token %7) ]
|
||||
cleanupret from %7 unwind to caller
|
||||
}
|
||||
|
||||
; When a function does not have stack-allocated objects, it does not need to
|
||||
; store SP back to __stack_pointer global at the epilog.
|
||||
;
|
||||
; void foo();
|
||||
; void no_sp_writeback() {
|
||||
; try {
|
||||
; foo();
|
||||
; } catch (...) {
|
||||
; }
|
||||
; }
|
||||
|
||||
; CHECK-LABEL: no_sp_writeback
|
||||
; CHECK: block
|
||||
; CHECK: block i32
|
||||
; CHECK: try_table (catch __cpp_exception 0)
|
||||
; CHECK: call foo
|
||||
; CHECK: br 2
|
||||
; CHECK: end_try_table
|
||||
; CHECK: end_block
|
||||
; CHECK: call __cxa_begin_catch
|
||||
; CHECK: call __cxa_end_catch
|
||||
; CHECK: end_block
|
||||
; CHECK-NOT: global.set __stack_pointer
|
||||
; CHECK: end_function
|
||||
define void @no_sp_writeback() personality ptr @__gxx_wasm_personality_v0 {
|
||||
entry:
|
||||
invoke void @foo()
|
||||
to label %try.cont unwind label %catch.dispatch
|
||||
|
||||
catch.dispatch: ; preds = %entry
|
||||
%0 = catchswitch within none [label %catch.start] unwind to caller
|
||||
|
||||
catch.start: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [ptr null]
|
||||
%2 = call ptr @llvm.wasm.get.exception(token %1)
|
||||
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
|
||||
%4 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
|
||||
call void @__cxa_end_catch() [ "funclet"(token %1) ]
|
||||
catchret from %1 to label %try.cont
|
||||
|
||||
try.cont: ; preds = %catch.start, %entry
|
||||
ret void
|
||||
}
|
||||
|
||||
; When the result of @llvm.wasm.get.exception is not used. This is created to
|
||||
; fix a bug in LateEHPrepare and this should not crash.
|
||||
define void @get_exception_wo_use() personality ptr @__gxx_wasm_personality_v0 {
|
||||
entry:
|
||||
invoke void @foo()
|
||||
to label %try.cont unwind label %catch.dispatch
|
||||
|
||||
catch.dispatch: ; preds = %entry
|
||||
%0 = catchswitch within none [label %catch.start] unwind to caller
|
||||
|
||||
catch.start: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [ptr null]
|
||||
%2 = call ptr @llvm.wasm.get.exception(token %1)
|
||||
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
|
||||
catchret from %1 to label %try.cont
|
||||
|
||||
try.cont: ; preds = %catch.start, %entry
|
||||
ret void
|
||||
}
|
||||
|
||||
; Tests a case when a cleanup region (cleanuppad ~ clanupret) contains another
|
||||
; catchpad
|
||||
define void @complex_cleanup_region() personality ptr @__gxx_wasm_personality_v0 {
|
||||
entry:
|
||||
invoke void @foo()
|
||||
to label %invoke.cont unwind label %ehcleanup
|
||||
|
||||
invoke.cont: ; preds = %entry
|
||||
ret void
|
||||
|
||||
ehcleanup: ; preds = %entry
|
||||
%0 = cleanuppad within none []
|
||||
invoke void @foo() [ "funclet"(token %0) ]
|
||||
to label %ehcleanupret unwind label %catch.dispatch
|
||||
|
||||
catch.dispatch: ; preds = %ehcleanup
|
||||
%1 = catchswitch within %0 [label %catch.start] unwind label %ehcleanup.1
|
||||
|
||||
catch.start: ; preds = %catch.dispatch
|
||||
%2 = catchpad within %1 [ptr null]
|
||||
%3 = call ptr @llvm.wasm.get.exception(token %2)
|
||||
%4 = call i32 @llvm.wasm.get.ehselector(token %2)
|
||||
catchret from %2 to label %ehcleanupret
|
||||
|
||||
ehcleanup.1: ; preds = %catch.dispatch
|
||||
%5 = cleanuppad within %0 []
|
||||
unreachable
|
||||
|
||||
ehcleanupret: ; preds = %catch.start, %ehcleanup
|
||||
cleanupret from %0 unwind to caller
|
||||
}
|
||||
|
||||
; Regression test for the bug that 'rethrow' was not treated correctly as a
|
||||
; terminator in isel.
|
||||
define void @rethrow_terminator() personality ptr @__gxx_wasm_personality_v0 {
|
||||
entry:
|
||||
invoke void @foo()
|
||||
to label %try.cont unwind label %catch.dispatch
|
||||
|
||||
catch.dispatch: ; preds = %entry
|
||||
%0 = catchswitch within none [label %catch.start] unwind label %ehcleanup
|
||||
|
||||
catch.start: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [ptr @_ZTIi]
|
||||
%2 = call ptr @llvm.wasm.get.exception(token %1)
|
||||
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
|
||||
%4 = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
|
||||
%matches = icmp eq i32 %3, %4
|
||||
br i1 %matches, label %catch, label %rethrow
|
||||
|
||||
catch: ; preds = %catch.start
|
||||
%5 = call ptr @__cxa_begin_catch(ptr %2) [ "funclet"(token %1) ]
|
||||
%6 = load i32, ptr %5, align 4
|
||||
call void @__cxa_end_catch() [ "funclet"(token %1) ]
|
||||
catchret from %1 to label %try.cont
|
||||
|
||||
rethrow: ; preds = %catch.start
|
||||
invoke void @llvm.wasm.rethrow() #1 [ "funclet"(token %1) ]
|
||||
to label %unreachable unwind label %ehcleanup
|
||||
|
||||
try.cont: ; preds = %entry, %catch
|
||||
ret void
|
||||
|
||||
ehcleanup: ; preds = %rethrow, %catch.dispatch
|
||||
; 'rethrow' BB is this BB's predecessor, and its
|
||||
; 'invoke void @llvm.wasm.rethrow()' is lowered down to a 'RETHROW' in Wasm
|
||||
; MIR. And this 'phi' creates 'CONST_I32' instruction in the predecessor
|
||||
; 'rethrow' BB. If 'RETHROW' is not treated correctly as a terminator, it can
|
||||
; create a BB like
|
||||
; bb.3.rethrow:
|
||||
; RETHROW 0
|
||||
; %0 = CONST_I32 20
|
||||
; BR ...
|
||||
%tmp = phi i32 [ 10, %catch.dispatch ], [ 20, %rethrow ]
|
||||
%7 = cleanuppad within none []
|
||||
call void @take_i32(i32 %tmp) [ "funclet"(token %7) ]
|
||||
cleanupret from %7 unwind to caller
|
||||
|
||||
unreachable: ; preds = %rethrow
|
||||
unreachable
|
||||
}
|
||||
|
||||
|
||||
declare void @foo()
|
||||
declare void @bar(ptr)
|
||||
declare void @take_i32(i32)
|
||||
declare i32 @__gxx_wasm_personality_v0(...)
|
||||
; Function Attrs: noreturn
|
||||
declare void @llvm.wasm.throw(i32, ptr) #1
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @llvm.wasm.get.exception(token) #0
|
||||
; Function Attrs: nounwind
|
||||
declare i32 @llvm.wasm.get.ehselector(token) #0
|
||||
; Function Attrs: noreturn
|
||||
declare void @llvm.wasm.rethrow() #1
|
||||
; Function Attrs: nounwind
|
||||
declare i32 @llvm.eh.typeid.for(ptr) #0
|
||||
declare ptr @__cxa_begin_catch(ptr)
|
||||
declare void @__cxa_end_catch()
|
||||
declare void @_ZSt9terminatev()
|
||||
declare ptr @_ZN4TempD2Ev(ptr returned)
|
||||
|
||||
attributes #0 = { nounwind }
|
||||
attributes #1 = { noreturn }
|
||||
|
||||
; CHECK: __cpp_exception:
|
||||
Reference in New Issue
Block a user