forked from OSchip/llvm-project
389 lines
14 KiB
C++
389 lines
14 KiB
C++
//=== WebAssemblyLateEHPrepare.cpp - WebAssembly Exception Preparation -===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// \brief Does various transformations for exception handling.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
|
|
#include "WebAssembly.h"
|
|
#include "WebAssemblySubtarget.h"
|
|
#include "WebAssemblyUtilities.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
|
#include "llvm/CodeGen/WasmEHFuncInfo.h"
|
|
#include "llvm/MC/MCAsmInfo.h"
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "wasm-late-eh-prepare"
|
|
|
|
namespace {
|
|
class WebAssemblyLateEHPrepare final : public MachineFunctionPass {
|
|
StringRef getPassName() const override {
|
|
return "WebAssembly Late Prepare Exception";
|
|
}
|
|
|
|
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
bool addCatches(MachineFunction &MF);
|
|
bool replaceFuncletReturns(MachineFunction &MF);
|
|
bool removeUnnecessaryUnreachables(MachineFunction &MF);
|
|
bool addExceptionExtraction(MachineFunction &MF);
|
|
bool restoreStackPointer(MachineFunction &MF);
|
|
|
|
public:
|
|
static char ID; // Pass identification, replacement for typeid
|
|
WebAssemblyLateEHPrepare() : MachineFunctionPass(ID) {}
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
char WebAssemblyLateEHPrepare::ID = 0;
|
|
INITIALIZE_PASS(WebAssemblyLateEHPrepare, DEBUG_TYPE,
|
|
"WebAssembly Late Exception Preparation", false, false)
|
|
|
|
FunctionPass *llvm::createWebAssemblyLateEHPrepare() {
|
|
return new WebAssemblyLateEHPrepare();
|
|
}
|
|
|
|
// Returns the nearest EH pad that dominates this instruction. This does not use
|
|
// dominator analysis; it just does BFS on its predecessors until arriving at an
|
|
// EH pad. This assumes valid EH scopes so the first EH pad it arrives in all
|
|
// possible search paths should be the same.
|
|
// Returns nullptr in case it does not find any EH pad in the search, or finds
|
|
// multiple different EH pads.
|
|
static MachineBasicBlock *getMatchingEHPad(MachineInstr *MI) {
|
|
MachineFunction *MF = MI->getParent()->getParent();
|
|
SmallVector<MachineBasicBlock *, 2> WL;
|
|
SmallPtrSet<MachineBasicBlock *, 2> Visited;
|
|
WL.push_back(MI->getParent());
|
|
MachineBasicBlock *EHPad = nullptr;
|
|
while (!WL.empty()) {
|
|
MachineBasicBlock *MBB = WL.pop_back_val();
|
|
if (Visited.count(MBB))
|
|
continue;
|
|
Visited.insert(MBB);
|
|
if (MBB->isEHPad()) {
|
|
if (EHPad && EHPad != MBB)
|
|
return nullptr;
|
|
EHPad = MBB;
|
|
continue;
|
|
}
|
|
if (MBB == &MF->front())
|
|
return nullptr;
|
|
WL.append(MBB->pred_begin(), MBB->pred_end());
|
|
}
|
|
return EHPad;
|
|
}
|
|
|
|
// Erase the specified BBs if the BB does not have any remaining predecessors,
|
|
// and also all its dead children.
|
|
template <typename Container>
|
|
static void eraseDeadBBsAndChildren(const Container &MBBs) {
|
|
SmallVector<MachineBasicBlock *, 8> WL(MBBs.begin(), MBBs.end());
|
|
while (!WL.empty()) {
|
|
MachineBasicBlock *MBB = WL.pop_back_val();
|
|
if (!MBB->pred_empty())
|
|
continue;
|
|
SmallVector<MachineBasicBlock *, 4> Succs(MBB->succ_begin(),
|
|
MBB->succ_end());
|
|
WL.append(MBB->succ_begin(), MBB->succ_end());
|
|
for (auto *Succ : Succs)
|
|
MBB->removeSuccessor(Succ);
|
|
MBB->eraseFromParent();
|
|
}
|
|
}
|
|
|
|
bool WebAssemblyLateEHPrepare::runOnMachineFunction(MachineFunction &MF) {
|
|
LLVM_DEBUG(dbgs() << "********** Late EH Prepare **********\n"
|
|
"********** Function: "
|
|
<< MF.getName() << '\n');
|
|
|
|
if (MF.getTarget().getMCAsmInfo()->getExceptionHandlingType() !=
|
|
ExceptionHandling::Wasm)
|
|
return false;
|
|
|
|
bool Changed = false;
|
|
if (MF.getFunction().hasPersonalityFn()) {
|
|
Changed |= addCatches(MF);
|
|
Changed |= replaceFuncletReturns(MF);
|
|
}
|
|
Changed |= removeUnnecessaryUnreachables(MF);
|
|
if (MF.getFunction().hasPersonalityFn()) {
|
|
Changed |= addExceptionExtraction(MF);
|
|
Changed |= restoreStackPointer(MF);
|
|
}
|
|
return Changed;
|
|
}
|
|
|
|
// Add catch instruction to beginning of catchpads and cleanuppads.
|
|
bool WebAssemblyLateEHPrepare::addCatches(MachineFunction &MF) {
|
|
bool Changed = false;
|
|
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
|
|
MachineRegisterInfo &MRI = MF.getRegInfo();
|
|
for (auto &MBB : MF) {
|
|
if (MBB.isEHPad()) {
|
|
Changed = true;
|
|
auto InsertPos = MBB.begin();
|
|
if (InsertPos->isEHLabel()) // EH pad starts with an EH label
|
|
++InsertPos;
|
|
unsigned DstReg =
|
|
MRI.createVirtualRegister(&WebAssembly::EXCEPT_REFRegClass);
|
|
BuildMI(MBB, InsertPos, MBB.begin()->getDebugLoc(),
|
|
TII.get(WebAssembly::CATCH), DstReg);
|
|
}
|
|
}
|
|
return Changed;
|
|
}
|
|
|
|
bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
|
|
bool Changed = false;
|
|
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
|
|
|
|
for (auto &MBB : MF) {
|
|
auto Pos = MBB.getFirstTerminator();
|
|
if (Pos == MBB.end())
|
|
continue;
|
|
MachineInstr *TI = &*Pos;
|
|
|
|
switch (TI->getOpcode()) {
|
|
case WebAssembly::CATCHRET: {
|
|
// Replace a catchret with a branch
|
|
MachineBasicBlock *TBB = TI->getOperand(0).getMBB();
|
|
if (!MBB.isLayoutSuccessor(TBB))
|
|
BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::BR))
|
|
.addMBB(TBB);
|
|
TI->eraseFromParent();
|
|
Changed = true;
|
|
break;
|
|
}
|
|
case WebAssembly::CLEANUPRET:
|
|
case WebAssembly::RETHROW_IN_CATCH: {
|
|
// Replace a cleanupret/rethrow_in_catch with a rethrow
|
|
auto *EHPad = getMatchingEHPad(TI);
|
|
auto CatchPos = EHPad->begin();
|
|
if (CatchPos->isEHLabel()) // EH pad starts with an EH label
|
|
++CatchPos;
|
|
MachineInstr *Catch = &*CatchPos;
|
|
unsigned ExnReg = Catch->getOperand(0).getReg();
|
|
BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::RETHROW))
|
|
.addReg(ExnReg);
|
|
TI->eraseFromParent();
|
|
Changed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Changed;
|
|
}
|
|
|
|
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)
|
|
continue;
|
|
Changed = true;
|
|
|
|
// The instruction after the throw should be an unreachable or a branch to
|
|
// another BB that should eventually lead to an unreachable. Delete it
|
|
// because throw itself is a terminator, and also delete successors if
|
|
// any.
|
|
MBB.erase(std::next(MI.getIterator()), MBB.end());
|
|
SmallVector<MachineBasicBlock *, 8> Succs(MBB.succ_begin(),
|
|
MBB.succ_end());
|
|
for (auto *Succ : Succs)
|
|
if (!Succ->isEHPad())
|
|
MBB.removeSuccessor(Succ);
|
|
eraseDeadBBsAndChildren(Succs);
|
|
}
|
|
}
|
|
|
|
return Changed;
|
|
}
|
|
|
|
// Wasm uses 'br_on_exn' instruction to check the tag of an exception. It takes
|
|
// except_ref type object returned by 'catch', and branches to the destination
|
|
// if it matches a given tag. We currently use __cpp_exception symbol to
|
|
// represent the tag for all C++ exceptions.
|
|
//
|
|
// block $l (result i32)
|
|
// ...
|
|
// ;; except_ref $e is on the stack at this point
|
|
// br_on_exn $l $e ;; branch to $l with $e's arguments
|
|
// ...
|
|
// end
|
|
// ;; Here we expect the extracted values are on top of the wasm value stack
|
|
// ... Handle exception using values ...
|
|
//
|
|
// br_on_exn takes an except_ref object and branches if it matches the given
|
|
// tag. There can be multiple br_on_exn instructions if we want to match for
|
|
// another tag, but for now we only test for __cpp_exception tag, and if it does
|
|
// not match, i.e., it is a foreign exception, we rethrow it.
|
|
//
|
|
// In the destination BB that's the target of br_on_exn, extracted exception
|
|
// values (in C++'s case a single i32, which represents an exception pointer)
|
|
// are placed on top of the wasm stack. Because we can't model wasm stack in
|
|
// LLVM instruction, we use 'extract_exception' pseudo instruction to retrieve
|
|
// it. The pseudo instruction will be deleted later.
|
|
bool WebAssemblyLateEHPrepare::addExceptionExtraction(MachineFunction &MF) {
|
|
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
|
|
auto *EHInfo = MF.getWasmEHFuncInfo();
|
|
SmallVector<MachineInstr *, 16> ExtractInstrs;
|
|
SmallVector<MachineInstr *, 8> ToDelete;
|
|
for (auto &MBB : MF) {
|
|
for (auto &MI : MBB) {
|
|
if (MI.getOpcode() == WebAssembly::EXTRACT_EXCEPTION_I32) {
|
|
if (MI.getOperand(0).isDead())
|
|
ToDelete.push_back(&MI);
|
|
else
|
|
ExtractInstrs.push_back(&MI);
|
|
}
|
|
}
|
|
}
|
|
bool Changed = !ToDelete.empty() || !ExtractInstrs.empty();
|
|
for (auto *MI : ToDelete)
|
|
MI->eraseFromParent();
|
|
if (ExtractInstrs.empty())
|
|
return Changed;
|
|
|
|
// Find terminate pads.
|
|
SmallSet<MachineBasicBlock *, 8> TerminatePads;
|
|
for (auto &MBB : MF) {
|
|
for (auto &MI : MBB) {
|
|
if (MI.isCall()) {
|
|
const MachineOperand &CalleeOp = MI.getOperand(0);
|
|
if (CalleeOp.isGlobal() && CalleeOp.getGlobal()->getName() ==
|
|
WebAssembly::ClangCallTerminateFn)
|
|
TerminatePads.insert(getMatchingEHPad(&MI));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto *Extract : ExtractInstrs) {
|
|
MachineBasicBlock *EHPad = getMatchingEHPad(Extract);
|
|
assert(EHPad && "No matching EH pad for extract_exception");
|
|
auto CatchPos = EHPad->begin();
|
|
if (CatchPos->isEHLabel()) // EH pad starts with an EH label
|
|
++CatchPos;
|
|
MachineInstr *Catch = &*CatchPos;
|
|
|
|
if (Catch->getNextNode() != Extract)
|
|
EHPad->insert(Catch->getNextNode(), Extract->removeFromParent());
|
|
|
|
// - Before:
|
|
// ehpad:
|
|
// %exnref:except_ref = catch
|
|
// %exn:i32 = extract_exception
|
|
// ... use exn ...
|
|
//
|
|
// - After:
|
|
// ehpad:
|
|
// %exnref:except_ref = catch
|
|
// br_on_exn %thenbb, $__cpp_exception, %exnref
|
|
// br %elsebb
|
|
// elsebb:
|
|
// rethrow
|
|
// thenbb:
|
|
// %exn:i32 = extract_exception
|
|
// ... use exn ...
|
|
unsigned ExnReg = Catch->getOperand(0).getReg();
|
|
auto *ThenMBB = MF.CreateMachineBasicBlock();
|
|
auto *ElseMBB = MF.CreateMachineBasicBlock();
|
|
MF.insert(std::next(MachineFunction::iterator(EHPad)), ElseMBB);
|
|
MF.insert(std::next(MachineFunction::iterator(ElseMBB)), ThenMBB);
|
|
ThenMBB->splice(ThenMBB->end(), EHPad, Extract, EHPad->end());
|
|
ThenMBB->transferSuccessors(EHPad);
|
|
EHPad->addSuccessor(ThenMBB);
|
|
EHPad->addSuccessor(ElseMBB);
|
|
|
|
DebugLoc DL = Extract->getDebugLoc();
|
|
const char *CPPExnSymbol = MF.createExternalSymbolName("__cpp_exception");
|
|
BuildMI(EHPad, DL, TII.get(WebAssembly::BR_ON_EXN))
|
|
.addMBB(ThenMBB)
|
|
.addExternalSymbol(CPPExnSymbol, WebAssemblyII::MO_SYMBOL_EVENT)
|
|
.addReg(ExnReg);
|
|
BuildMI(EHPad, DL, TII.get(WebAssembly::BR)).addMBB(ElseMBB);
|
|
|
|
// When this is a terminate pad with __clang_call_terminate() call, we don't
|
|
// rethrow it anymore and call __clang_call_terminate() with a nullptr
|
|
// argument, which will call std::terminate().
|
|
//
|
|
// - Before:
|
|
// ehpad:
|
|
// %exnref:except_ref = catch
|
|
// %exn:i32 = extract_exception
|
|
// call @__clang_call_terminate(%exn)
|
|
// unreachable
|
|
//
|
|
// - After:
|
|
// ehpad:
|
|
// %exnref:except_ref = catch
|
|
// br_on_exn %thenbb, $__cpp_exception, %exnref
|
|
// br %elsebb
|
|
// elsebb:
|
|
// call @__clang_call_terminate(0)
|
|
// unreachable
|
|
// thenbb:
|
|
// %exn:i32 = extract_exception
|
|
// call @__clang_call_terminate(%exn)
|
|
// unreachable
|
|
if (TerminatePads.count(EHPad)) {
|
|
Function *ClangCallTerminateFn =
|
|
MF.getFunction().getParent()->getFunction(
|
|
WebAssembly::ClangCallTerminateFn);
|
|
assert(ClangCallTerminateFn &&
|
|
"There is no __clang_call_terminate() function");
|
|
BuildMI(ElseMBB, DL, TII.get(WebAssembly::CALL_VOID))
|
|
.addGlobalAddress(ClangCallTerminateFn)
|
|
.addImm(0);
|
|
BuildMI(ElseMBB, DL, TII.get(WebAssembly::UNREACHABLE));
|
|
|
|
} else {
|
|
BuildMI(ElseMBB, DL, TII.get(WebAssembly::RETHROW)).addReg(ExnReg);
|
|
if (EHInfo->hasEHPadUnwindDest(EHPad))
|
|
ElseMBB->addSuccessor(EHInfo->getEHPadUnwindDest(EHPad));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// After the stack is unwound due to a thrown exception, the __stack_pointer
|
|
// global can point to an invalid address. This inserts instructions that
|
|
// restore __stack_pointer global.
|
|
bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) {
|
|
const auto *FrameLowering = static_cast<const WebAssemblyFrameLowering *>(
|
|
MF.getSubtarget().getFrameLowering());
|
|
if (!FrameLowering->needsPrologForEH(MF))
|
|
return false;
|
|
bool Changed = false;
|
|
|
|
for (auto &MBB : MF) {
|
|
if (!MBB.isEHPad())
|
|
continue;
|
|
Changed = true;
|
|
|
|
// Insert __stack_pointer restoring instructions at the beginning of each EH
|
|
// pad, after the catch instruction. Here it is safe to assume that SP32
|
|
// holds the latest value of __stack_pointer, because the only exception for
|
|
// this case is when a function uses the red zone, but that only happens
|
|
// with leaf functions, and we don't restore __stack_pointer in leaf
|
|
// functions anyway.
|
|
auto InsertPos = MBB.begin();
|
|
if (InsertPos->isEHLabel()) // EH pad starts with an EH label
|
|
++InsertPos;
|
|
if (InsertPos->getOpcode() == WebAssembly::CATCH)
|
|
++InsertPos;
|
|
FrameLowering->writeSPToGlobal(WebAssembly::SP32, MF, MBB, InsertPos,
|
|
MBB.begin()->getDebugLoc());
|
|
}
|
|
return Changed;
|
|
}
|