mirror of
https://github.com/RPCS3/llvm.git
synced 2024-11-27 21:50:29 +00:00
[WebAssembly] Add WebAssemblyException information analysis
Summary: A WebAssemblyException object contains BBs that belong to a 'catch' part of the try-catch-end structure. Because CFGSort requires all the BBs within a catch part to be sorted together as it does for loops, this pass calculates the nesting structure of catch part of exceptions in a function. Now this assumes the use of Windows EH instructions. Reviewers: dschuff, majnemer Subscribers: jfb, mgorny, sbc100, jgravelle-google, sunfish, llvm-commits Differential Revision: https://reviews.llvm.org/D44134 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@335439 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
6aa7ab2443
commit
ad04e25e24
@ -37,9 +37,9 @@ public:
|
||||
|
||||
MachineDominanceFrontier();
|
||||
|
||||
DominanceFrontierBase<MachineBasicBlock, false> &getBase() { return Base; }
|
||||
ForwardDominanceFrontierBase<MachineBasicBlock> &getBase() { return Base; }
|
||||
|
||||
const SmallVectorImpl<MachineBasicBlock *> &getRoots() const {
|
||||
const SmallVectorImpl<MachineBasicBlock *> &getRoots() const {
|
||||
return Base.getRoots();
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ add_llvm_target(WebAssemblyCodeGen
|
||||
WebAssemblyCFGStackify.cpp
|
||||
WebAssemblyCFGSort.cpp
|
||||
WebAssemblyLateEHPrepare.cpp
|
||||
WebAssemblyExceptionInfo.cpp
|
||||
WebAssemblyExplicitLocals.cpp
|
||||
WebAssemblyFastISel.cpp
|
||||
WebAssemblyFixIrreducibleControlFlow.cpp
|
||||
|
@ -70,6 +70,7 @@ void initializeWebAssemblyRegColoringPass(PassRegistry &);
|
||||
void initializeWebAssemblyExplicitLocalsPass(PassRegistry &);
|
||||
void initializeWebAssemblyFixIrreducibleControlFlowPass(PassRegistry &);
|
||||
void initializeWebAssemblyLateEHPreparePass(PassRegistry &);
|
||||
void initializeWebAssemblyExceptionInfoPass(PassRegistry &);
|
||||
void initializeWebAssemblyCFGSortPass(PassRegistry &);
|
||||
void initializeWebAssemblyCFGStackifyPass(PassRegistry &);
|
||||
void initializeWebAssemblyLowerBrUnlessPass(PassRegistry &);
|
||||
|
197
lib/Target/WebAssembly/WebAssemblyExceptionInfo.cpp
Normal file
197
lib/Target/WebAssembly/WebAssemblyExceptionInfo.cpp
Normal file
@ -0,0 +1,197 @@
|
||||
//===--- WebAssemblyExceptionInfo.cpp - Exception Infomation --------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// \file
|
||||
/// \brief This file implements WebAssemblyException information analysis.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "WebAssemblyExceptionInfo.h"
|
||||
#include "WebAssemblyUtilities.h"
|
||||
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
|
||||
#include "llvm/ADT/PostOrderIterator.h"
|
||||
#include "llvm/CodeGen/MachineDominanceFrontier.h"
|
||||
#include "llvm/CodeGen/MachineDominators.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
#define DEBUG_TYPE "wasm-exception-info"
|
||||
|
||||
char WebAssemblyExceptionInfo::ID = 0;
|
||||
|
||||
INITIALIZE_PASS_BEGIN(WebAssemblyExceptionInfo, DEBUG_TYPE,
|
||||
"WebAssembly Exception Information", true, true)
|
||||
INITIALIZE_PASS_DEPENDENCY(MachineDominatorTree)
|
||||
INITIALIZE_PASS_DEPENDENCY(MachineDominanceFrontier)
|
||||
INITIALIZE_PASS_END(WebAssemblyExceptionInfo, DEBUG_TYPE,
|
||||
"WebAssembly Exception Information", true, true)
|
||||
|
||||
bool WebAssemblyExceptionInfo::runOnMachineFunction(MachineFunction &F) {
|
||||
releaseMemory();
|
||||
auto &MDT = getAnalysis<MachineDominatorTree>();
|
||||
auto &MDF = getAnalysis<MachineDominanceFrontier>();
|
||||
recalculate(MDT, MDF);
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebAssemblyExceptionInfo::recalculate(
|
||||
MachineDominatorTree &MDT, const MachineDominanceFrontier &MDF) {
|
||||
// Postorder traversal of the dominator tree.
|
||||
SmallVector<WebAssemblyException *, 8> Exceptions;
|
||||
for (auto DomNode : post_order(&MDT)) {
|
||||
MachineBasicBlock *EHPad = DomNode->getBlock();
|
||||
if (!EHPad->isEHPad())
|
||||
continue;
|
||||
// We group catch & catch-all terminate pads together, so skip the second
|
||||
// one
|
||||
if (WebAssembly::isCatchAllTerminatePad(*EHPad))
|
||||
continue;
|
||||
auto *WE = new WebAssemblyException(EHPad);
|
||||
discoverAndMapException(WE, MDT, MDF);
|
||||
Exceptions.push_back(WE);
|
||||
}
|
||||
|
||||
// Add BBs to exceptions
|
||||
for (auto DomNode : post_order(&MDT)) {
|
||||
MachineBasicBlock *MBB = DomNode->getBlock();
|
||||
WebAssemblyException *WE = getExceptionFor(MBB);
|
||||
for (; WE; WE = WE->getParentException())
|
||||
WE->addBlock(MBB);
|
||||
}
|
||||
|
||||
// Add subexceptions to exceptions
|
||||
for (auto *WE : Exceptions) {
|
||||
if (WE->getParentException())
|
||||
WE->getParentException()->getSubExceptions().push_back(WE);
|
||||
else
|
||||
addTopLevelException(WE);
|
||||
}
|
||||
|
||||
// For convenience, Blocks and SubExceptions are inserted in postorder.
|
||||
// Reverse the lists.
|
||||
for (auto *WE : Exceptions) {
|
||||
WE->reverseBlock();
|
||||
std::reverse(WE->getSubExceptions().begin(), WE->getSubExceptions().end());
|
||||
}
|
||||
}
|
||||
|
||||
void WebAssemblyExceptionInfo::releaseMemory() {
|
||||
BBMap.clear();
|
||||
DeleteContainerPointers(TopLevelExceptions);
|
||||
TopLevelExceptions.clear();
|
||||
}
|
||||
|
||||
void WebAssemblyExceptionInfo::getAnalysisUsage(AnalysisUsage &AU) const {
|
||||
AU.setPreservesAll();
|
||||
AU.addRequired<MachineDominatorTree>();
|
||||
AU.addRequired<MachineDominanceFrontier>();
|
||||
MachineFunctionPass::getAnalysisUsage(AU);
|
||||
}
|
||||
|
||||
void WebAssemblyExceptionInfo::discoverAndMapException(
|
||||
WebAssemblyException *WE, const MachineDominatorTree &MDT,
|
||||
const MachineDominanceFrontier &MDF) {
|
||||
unsigned NumBlocks = 0;
|
||||
unsigned NumSubExceptions = 0;
|
||||
|
||||
// Map blocks that belong to a catchpad / cleanuppad
|
||||
MachineBasicBlock *EHPad = WE->getEHPad();
|
||||
|
||||
// We group catch & catch-all terminate pads together within an exception
|
||||
if (WebAssembly::isCatchTerminatePad(*EHPad)) {
|
||||
assert(EHPad->succ_size() == 1 &&
|
||||
"Catch terminate pad has more than one successors");
|
||||
changeExceptionFor(EHPad, WE);
|
||||
changeExceptionFor(*(EHPad->succ_begin()), WE);
|
||||
return;
|
||||
}
|
||||
|
||||
SmallVector<MachineBasicBlock *, 8> WL;
|
||||
WL.push_back(EHPad);
|
||||
while (!WL.empty()) {
|
||||
MachineBasicBlock *MBB = WL.pop_back_val();
|
||||
|
||||
// Find its outermost discovered exception. If this is a discovered block,
|
||||
// check if it is already discovered to be a subexception of this exception.
|
||||
WebAssemblyException *SubE = getOutermostException(MBB);
|
||||
if (SubE) {
|
||||
if (SubE != WE) {
|
||||
// Discover a subexception of this exception.
|
||||
SubE->setParentException(WE);
|
||||
++NumSubExceptions;
|
||||
NumBlocks += SubE->getBlocksVector().capacity();
|
||||
// All blocks that belong to this subexception have been already
|
||||
// discovered. Skip all of them. Add the subexception's landing pad's
|
||||
// dominance frontier to the worklist.
|
||||
for (auto &Frontier : MDF.find(SubE->getEHPad())->second)
|
||||
if (MDT.dominates(EHPad, Frontier))
|
||||
WL.push_back(Frontier);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is an undiscovered block. Map it to the current exception.
|
||||
changeExceptionFor(MBB, WE);
|
||||
++NumBlocks;
|
||||
|
||||
// Add successors dominated by the current BB to the worklist.
|
||||
for (auto *Succ : MBB->successors())
|
||||
if (MDT.dominates(EHPad, Succ))
|
||||
WL.push_back(Succ);
|
||||
}
|
||||
|
||||
WE->getSubExceptions().reserve(NumSubExceptions);
|
||||
WE->reserveBlocks(NumBlocks);
|
||||
}
|
||||
|
||||
WebAssemblyException *
|
||||
WebAssemblyExceptionInfo::getOutermostException(MachineBasicBlock *MBB) const {
|
||||
WebAssemblyException *WE = getExceptionFor(MBB);
|
||||
if (WE) {
|
||||
while (WebAssemblyException *Parent = WE->getParentException())
|
||||
WE = Parent;
|
||||
}
|
||||
return WE;
|
||||
}
|
||||
|
||||
void WebAssemblyException::print(raw_ostream &OS, unsigned Depth) const {
|
||||
OS.indent(Depth * 2) << "Exception at depth " << getExceptionDepth()
|
||||
<< " containing: ";
|
||||
|
||||
for (unsigned I = 0; I < getBlocks().size(); ++I) {
|
||||
MachineBasicBlock *MBB = getBlocks()[I];
|
||||
if (I)
|
||||
OS << ", ";
|
||||
OS << "%bb." << MBB->getNumber();
|
||||
if (const auto *BB = MBB->getBasicBlock())
|
||||
if (BB->hasName())
|
||||
OS << "." << BB->getName();
|
||||
|
||||
if (getEHPad() == MBB)
|
||||
OS << " (landing-pad)";
|
||||
}
|
||||
OS << "\n";
|
||||
|
||||
for (auto &SubE : SubExceptions)
|
||||
SubE->print(OS, Depth + 2);
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
|
||||
LLVM_DUMP_METHOD void WebAssemblyException::dump() const { print(dbgs()); }
|
||||
#endif
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const WebAssemblyException &WE) {
|
||||
WE.print(OS);
|
||||
return OS;
|
||||
}
|
||||
|
||||
void WebAssemblyExceptionInfo::print(raw_ostream &OS, const Module *) const {
|
||||
for (auto *WE : TopLevelExceptions)
|
||||
WE->print(OS);
|
||||
}
|
170
lib/Target/WebAssembly/WebAssemblyExceptionInfo.h
Normal file
170
lib/Target/WebAssembly/WebAssemblyExceptionInfo.h
Normal file
@ -0,0 +1,170 @@
|
||||
//===-- WebAssemblyExceptionInfo.h - WebAssembly Exception Info -*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// \file
|
||||
/// \brief This file implements WebAssemblyException information analysis.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIB_TARGET_WEBASSEMBLY_WEBASSEMBLYEXCEPTIONINFO_H
|
||||
#define LLVM_LIB_TARGET_WEBASSEMBLY_WEBASSEMBLYEXCEPTIONINFO_H
|
||||
|
||||
#include "WebAssembly.h"
|
||||
#include "llvm/ADT/SetVector.h"
|
||||
#include "llvm/CodeGen/MachineFunctionPass.h"
|
||||
|
||||
namespace llvm {
|
||||
|
||||
class MachineDominatorTree;
|
||||
class MachineDominanceFrontier;
|
||||
|
||||
// WebAssembly instructions for exception handling are structured as follows:
|
||||
// try
|
||||
// instructions*
|
||||
// catch ----|
|
||||
// instructions* | -> A WebAssemblyException consists of this region
|
||||
// end ----|
|
||||
//
|
||||
// A WebAssemblyException object contains BBs that belong to a 'catch' part of
|
||||
// the try-catch-end structure to be created later. 'try' and 'end' markers
|
||||
// are not present at this stage and will be generated in CFGStackify pass.
|
||||
// Because CFGSort requires all the BBs within a catch part to be sorted
|
||||
// together as it does for loops, this pass calculates the nesting structure of
|
||||
// catch part of exceptions in a function.
|
||||
//
|
||||
// An exception catch part is defined as a BB with catch instruction and all
|
||||
// other BBs dominated by this BB.
|
||||
class WebAssemblyException {
|
||||
MachineBasicBlock *EHPad = nullptr;
|
||||
|
||||
WebAssemblyException *ParentException = nullptr;
|
||||
std::vector<WebAssemblyException *> SubExceptions;
|
||||
std::vector<MachineBasicBlock *> Blocks;
|
||||
SmallPtrSet<const MachineBasicBlock *, 8> BlockSet;
|
||||
|
||||
public:
|
||||
WebAssemblyException(MachineBasicBlock *EHPad) : EHPad(EHPad) {}
|
||||
~WebAssemblyException() { DeleteContainerPointers(SubExceptions); }
|
||||
WebAssemblyException(const WebAssemblyException &) = delete;
|
||||
const WebAssemblyException &operator=(const WebAssemblyException &) = delete;
|
||||
|
||||
MachineBasicBlock *getEHPad() const { return EHPad; }
|
||||
MachineBasicBlock *getHeader() const { return EHPad; }
|
||||
WebAssemblyException *getParentException() const { return ParentException; }
|
||||
void setParentException(WebAssemblyException *WE) { ParentException = WE; }
|
||||
|
||||
bool contains(const WebAssemblyException *WE) const {
|
||||
if (WE == this)
|
||||
return true;
|
||||
if (!WE)
|
||||
return false;
|
||||
return contains(WE->getParentException());
|
||||
}
|
||||
bool contains(const MachineBasicBlock *MBB) const {
|
||||
return BlockSet.count(MBB);
|
||||
}
|
||||
|
||||
void addBlock(MachineBasicBlock *MBB) {
|
||||
Blocks.push_back(MBB);
|
||||
BlockSet.insert(MBB);
|
||||
}
|
||||
ArrayRef<MachineBasicBlock *> getBlocks() const { return Blocks; }
|
||||
using block_iterator = typename ArrayRef<MachineBasicBlock *>::const_iterator;
|
||||
block_iterator block_begin() const { return getBlocks().begin(); }
|
||||
block_iterator block_end() const { return getBlocks().end(); }
|
||||
inline iterator_range<block_iterator> blocks() const {
|
||||
return make_range(block_begin(), block_end());
|
||||
}
|
||||
unsigned getNumBlocks() const { return Blocks.size(); }
|
||||
std::vector<MachineBasicBlock *> &getBlocksVector() { return Blocks; }
|
||||
|
||||
const std::vector<WebAssemblyException *> &getSubExceptions() const {
|
||||
return SubExceptions;
|
||||
}
|
||||
std::vector<WebAssemblyException *> &getSubExceptions() {
|
||||
return SubExceptions;
|
||||
}
|
||||
void addSubException(WebAssemblyException *E) { SubExceptions.push_back(E); }
|
||||
using iterator = typename std::vector<WebAssemblyException *>::const_iterator;
|
||||
iterator begin() const { return SubExceptions.begin(); }
|
||||
iterator end() const { return SubExceptions.end(); }
|
||||
|
||||
void reserveBlocks(unsigned Size) { Blocks.reserve(Size); }
|
||||
void reverseBlock(unsigned From = 0) {
|
||||
std::reverse(Blocks.begin() + From, Blocks.end());
|
||||
}
|
||||
|
||||
// Return the nesting level. An outermost one has depth 1.
|
||||
unsigned getExceptionDepth() const {
|
||||
unsigned D = 1;
|
||||
for (const WebAssemblyException *CurException = ParentException;
|
||||
CurException; CurException = CurException->ParentException)
|
||||
++D;
|
||||
return D;
|
||||
}
|
||||
|
||||
void print(raw_ostream &OS, unsigned Depth = 0) const;
|
||||
void dump() const;
|
||||
};
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const WebAssemblyException &WE);
|
||||
|
||||
class WebAssemblyExceptionInfo final : public MachineFunctionPass {
|
||||
// Mapping of basic blocks to the innermost exception they occur in
|
||||
DenseMap<const MachineBasicBlock *, WebAssemblyException *> BBMap;
|
||||
std::vector<WebAssemblyException *> TopLevelExceptions;
|
||||
|
||||
void discoverAndMapException(WebAssemblyException *WE,
|
||||
const MachineDominatorTree &MDT,
|
||||
const MachineDominanceFrontier &MDF);
|
||||
WebAssemblyException *getOutermostException(MachineBasicBlock *MBB) const;
|
||||
|
||||
public:
|
||||
static char ID;
|
||||
WebAssemblyExceptionInfo() : MachineFunctionPass(ID) {
|
||||
initializeWebAssemblyExceptionInfoPass(*PassRegistry::getPassRegistry());
|
||||
}
|
||||
~WebAssemblyExceptionInfo() override { releaseMemory(); }
|
||||
WebAssemblyExceptionInfo(const WebAssemblyExceptionInfo &) = delete;
|
||||
WebAssemblyExceptionInfo &
|
||||
operator=(const WebAssemblyExceptionInfo &) = delete;
|
||||
|
||||
bool runOnMachineFunction(MachineFunction &) override;
|
||||
void releaseMemory() override;
|
||||
void recalculate(MachineDominatorTree &MDT,
|
||||
const MachineDominanceFrontier &MDF);
|
||||
void getAnalysisUsage(AnalysisUsage &AU) const override;
|
||||
|
||||
bool empty() const { return TopLevelExceptions.empty(); }
|
||||
|
||||
// Return the innermost exception that MBB lives in. If the block is not in an
|
||||
// exception, null is returned.
|
||||
WebAssemblyException *getExceptionFor(const MachineBasicBlock *MBB) const {
|
||||
return BBMap.lookup(MBB);
|
||||
}
|
||||
|
||||
void changeExceptionFor(MachineBasicBlock *MBB, WebAssemblyException *WE) {
|
||||
if (!WE) {
|
||||
BBMap.erase(MBB);
|
||||
return;
|
||||
}
|
||||
BBMap[MBB] = WE;
|
||||
}
|
||||
|
||||
void addTopLevelException(WebAssemblyException *WE) {
|
||||
assert(!WE->getParentException() && "Not a top level exception!");
|
||||
TopLevelExceptions.push_back(WE);
|
||||
}
|
||||
|
||||
void print(raw_ostream &OS, const Module *M = nullptr) const override;
|
||||
};
|
||||
|
||||
} // end namespace llvm
|
||||
|
||||
#endif
|
@ -66,6 +66,7 @@ extern "C" void LLVMInitializeWebAssemblyTarget() {
|
||||
initializeWebAssemblyExplicitLocalsPass(PR);
|
||||
initializeWebAssemblyFixIrreducibleControlFlowPass(PR);
|
||||
initializeWebAssemblyLateEHPreparePass(PR);
|
||||
initializeWebAssemblyExceptionInfoPass(PR);
|
||||
initializeWebAssemblyCFGSortPass(PR);
|
||||
initializeWebAssemblyCFGStackifyPass(PR);
|
||||
initializeWebAssemblyLowerBrUnlessPass(PR);
|
||||
|
18
unittests/Target/WebAssembly/CMakeLists.txt
Normal file
18
unittests/Target/WebAssembly/CMakeLists.txt
Normal file
@ -0,0 +1,18 @@
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/lib/Target/WebAssembly
|
||||
${CMAKE_BINARY_DIR}/lib/Target/WebAssembly
|
||||
)
|
||||
|
||||
set(LLVM_LINK_COMPONENTS
|
||||
CodeGen
|
||||
Core
|
||||
MC
|
||||
MIRParser
|
||||
WebAssemblyCodeGen
|
||||
WebAssemblyDesc
|
||||
WebAssemblyInfo
|
||||
)
|
||||
|
||||
add_llvm_unittest(WebAssemblyTests
|
||||
WebAssemblyExceptionInfoTest.cpp
|
||||
)
|
549
unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp
Normal file
549
unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp
Normal file
@ -0,0 +1,549 @@
|
||||
//=== WebAssemblyExceptionInfoTest.cpp - WebAssebmlyExceptionInfo unit tests =//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "WebAssemblyExceptionInfo.h"
|
||||
#include "llvm/CodeGen/MIRParser/MIRParser.h"
|
||||
#include "llvm/CodeGen/MachineDominanceFrontier.h"
|
||||
#include "llvm/CodeGen/MachineDominators.h"
|
||||
#include "llvm/CodeGen/MachineModuleInfo.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
#include "llvm/Support/TargetRegistry.h"
|
||||
#include "llvm/Support/TargetSelect.h"
|
||||
#include "llvm/Target/TargetMachine.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<TargetMachine> createTargetMachine() {
|
||||
auto TT(Triple::normalize("wasm32-unknown-unknown"));
|
||||
std::string CPU("");
|
||||
std::string FS("");
|
||||
|
||||
LLVMInitializeWebAssemblyTargetInfo();
|
||||
LLVMInitializeWebAssemblyTarget();
|
||||
LLVMInitializeWebAssemblyTargetMC();
|
||||
|
||||
std::string Error;
|
||||
const Target *TheTarget = TargetRegistry::lookupTarget(TT, Error);
|
||||
assert(TheTarget);
|
||||
|
||||
return std::unique_ptr<TargetMachine>(TheTarget->createTargetMachine(
|
||||
TT, CPU, FS, TargetOptions(), None, None, CodeGenOpt::Default));
|
||||
}
|
||||
|
||||
std::unique_ptr<Module> parseMIR(LLVMContext &Context,
|
||||
std::unique_ptr<MIRParser> &MIR,
|
||||
const TargetMachine &TM, StringRef MIRCode,
|
||||
const char *FuncName, MachineModuleInfo &MMI) {
|
||||
SMDiagnostic Diagnostic;
|
||||
std::unique_ptr<MemoryBuffer> MBuffer = MemoryBuffer::getMemBuffer(MIRCode);
|
||||
MIR = createMIRParser(std::move(MBuffer), Context);
|
||||
if (!MIR)
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<Module> M = MIR->parseIRModule();
|
||||
if (!M)
|
||||
return nullptr;
|
||||
|
||||
M->setDataLayout(TM.createDataLayout());
|
||||
|
||||
if (MIR->parseMachineFunctions(*M, MMI))
|
||||
return nullptr;
|
||||
|
||||
return M;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(WebAssemblyExceptionInfoTest, TEST0) {
|
||||
std::unique_ptr<TargetMachine> TM = createTargetMachine();
|
||||
ASSERT_TRUE(TM);
|
||||
|
||||
StringRef MIRString = R"MIR(
|
||||
--- |
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32-unknown-unknown"
|
||||
|
||||
declare i32 @__gxx_wasm_personality_v0(...)
|
||||
|
||||
define hidden void @test0() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
|
||||
unreachable
|
||||
}
|
||||
|
||||
...
|
||||
---
|
||||
name: test0
|
||||
liveins:
|
||||
- { reg: '$arguments' }
|
||||
- { reg: '$value_stack' }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1, %bb.2
|
||||
liveins: $arguments, $value_stack
|
||||
BR %bb.1, implicit-def dead $arguments
|
||||
|
||||
bb.1:
|
||||
; predecessors: %bb.0
|
||||
successors: %bb.7
|
||||
liveins: $value_stack
|
||||
BR %bb.7, implicit-def $arguments
|
||||
|
||||
bb.2 (landing-pad):
|
||||
; predecessors: %bb.0
|
||||
successors: %bb.3, %bb.9
|
||||
liveins: $value_stack
|
||||
CATCH_ALL implicit-def $arguments
|
||||
CLEANUPRET implicit-def dead $arguments
|
||||
|
||||
bb.3 (landing-pad):
|
||||
; predecessors: %bb.2
|
||||
successors: %bb.4, %bb.6
|
||||
liveins: $value_stack
|
||||
CATCH_ALL implicit-def $arguments
|
||||
BR_IF %bb.4, %58:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
|
||||
BR %bb.6, implicit-def $arguments
|
||||
|
||||
bb.4:
|
||||
; predecessors: %bb.3
|
||||
successors: %bb.5, %bb.8
|
||||
liveins: $value_stack
|
||||
BR %bb.5, implicit-def dead $arguments
|
||||
|
||||
bb.5:
|
||||
; predecessors: %bb.4
|
||||
successors: %bb.7
|
||||
liveins: $value_stack
|
||||
CATCHRET %bb.7, %bb.0, implicit-def dead $arguments
|
||||
|
||||
bb.6:
|
||||
; predecessors: %bb.3
|
||||
successors: %bb.10, %bb.9
|
||||
liveins: $value_stack
|
||||
BR %bb.10, implicit-def dead $arguments
|
||||
|
||||
bb.7:
|
||||
; predecessors: %bb.5, %bb.1
|
||||
liveins: $value_stack
|
||||
RETURN_VOID implicit-def $arguments
|
||||
|
||||
bb.8 (landing-pad):
|
||||
; predecessors: %bb.4
|
||||
successors: %bb.9
|
||||
liveins: $value_stack
|
||||
CATCH_ALL implicit-def $arguments
|
||||
CLEANUPRET implicit-def dead $arguments
|
||||
|
||||
bb.9 (landing-pad):
|
||||
; predecessors: %bb.2, %bb.6, %bb.8
|
||||
liveins: $value_stack
|
||||
CATCH_ALL implicit-def $arguments
|
||||
CLEANUPRET implicit-def dead $arguments
|
||||
|
||||
bb.10:
|
||||
; predecessors: %bb.6
|
||||
liveins: $value_stack
|
||||
UNREACHABLE implicit-def $arguments
|
||||
)MIR";
|
||||
|
||||
LLVMContext Context;
|
||||
std::unique_ptr<MIRParser> MIR;
|
||||
MachineModuleInfo MMI(TM.get());
|
||||
std::unique_ptr<Module> M =
|
||||
parseMIR(Context, MIR, *TM, MIRString, "test0", MMI);
|
||||
ASSERT_TRUE(M);
|
||||
|
||||
Function *F = M->getFunction("test0");
|
||||
auto *MF = MMI.getMachineFunction(*F);
|
||||
ASSERT_TRUE(MF);
|
||||
|
||||
WebAssemblyExceptionInfo WEI;
|
||||
MachineDominatorTree MDT;
|
||||
MachineDominanceFrontier MDF;
|
||||
MDT.runOnMachineFunction(*MF);
|
||||
MDF.getBase().analyze(MDT.getBase());
|
||||
WEI.recalculate(MDT, MDF);
|
||||
|
||||
// Exception info structure:
|
||||
// |- bb2 (ehpad), bb3, bb4, bb5, bb6, bb8, bb9, bb10
|
||||
// |- bb3 (ehpad), bb4, bb5, bb6, bb8, bb10
|
||||
// |- bb8 (ehpad)
|
||||
// |- bb9 (ehpad)
|
||||
|
||||
auto *MBB2 = MF->getBlockNumbered(2);
|
||||
auto *WE0 = WEI.getExceptionFor(MBB2);
|
||||
ASSERT_TRUE(WE0);
|
||||
EXPECT_EQ(WE0->getEHPad(), MBB2);
|
||||
EXPECT_EQ(WE0->getParentException(), nullptr);
|
||||
EXPECT_EQ(WE0->getExceptionDepth(), (unsigned)1);
|
||||
|
||||
auto *MBB3 = MF->getBlockNumbered(3);
|
||||
auto *WE0_0 = WEI.getExceptionFor(MBB3);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
EXPECT_EQ(WE0_0->getParentException(), WE0);
|
||||
EXPECT_EQ(WE0_0->getExceptionDepth(), (unsigned)2);
|
||||
|
||||
auto *MBB4 = MF->getBlockNumbered(4);
|
||||
WE0_0 = WEI.getExceptionFor(MBB4);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
|
||||
auto *MBB5 = MF->getBlockNumbered(5);
|
||||
WE0_0 = WEI.getExceptionFor(MBB5);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
|
||||
auto *MBB6 = MF->getBlockNumbered(6);
|
||||
WE0_0 = WEI.getExceptionFor(MBB6);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
|
||||
auto *MBB10 = MF->getBlockNumbered(10);
|
||||
WE0_0 = WEI.getExceptionFor(MBB10);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
|
||||
auto *MBB8 = MF->getBlockNumbered(8);
|
||||
auto *WE0_0_0 = WEI.getExceptionFor(MBB8);
|
||||
ASSERT_TRUE(WE0_0_0);
|
||||
EXPECT_EQ(WE0_0_0->getEHPad(), MBB8);
|
||||
EXPECT_EQ(WE0_0_0->getParentException(), WE0_0);
|
||||
EXPECT_EQ(WE0_0_0->getExceptionDepth(), (unsigned)3);
|
||||
|
||||
auto *MBB9 = MF->getBlockNumbered(9);
|
||||
auto *WE0_1 = WEI.getExceptionFor(MBB9);
|
||||
ASSERT_TRUE(WE0_1);
|
||||
EXPECT_EQ(WE0_1->getEHPad(), MBB9);
|
||||
EXPECT_EQ(WE0_1->getParentException(), WE0);
|
||||
EXPECT_EQ(WE0_1->getExceptionDepth(), (unsigned)2);
|
||||
}
|
||||
|
||||
TEST(WebAssemblyExceptionInfoTest, TEST1) {
|
||||
std::unique_ptr<TargetMachine> TM = createTargetMachine();
|
||||
ASSERT_TRUE(TM);
|
||||
|
||||
StringRef MIRString = R"MIR(
|
||||
--- |
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32-unknown-unknown"
|
||||
|
||||
declare i32 @__gxx_wasm_personality_v0(...)
|
||||
|
||||
define hidden void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
|
||||
unreachable
|
||||
}
|
||||
|
||||
...
|
||||
---
|
||||
name: test1
|
||||
liveins:
|
||||
- { reg: '$arguments' }
|
||||
- { reg: '$value_stack' }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.9, %bb.1
|
||||
liveins: $arguments, $value_stack
|
||||
BR %bb.9, implicit-def dead $arguments
|
||||
|
||||
bb.1 (landing-pad):
|
||||
; predecessors: %bb.0
|
||||
successors: %bb.2, %bb.8
|
||||
liveins: $value_stack
|
||||
%52:i32 = CATCH_I32 0, implicit-def dead $arguments
|
||||
BR_IF %bb.2, %32:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
|
||||
BR %bb.8, implicit-def $arguments
|
||||
|
||||
bb.2:
|
||||
; predecessors: %bb.1
|
||||
successors: %bb.7, %bb.3, %bb.11
|
||||
liveins: $value_stack
|
||||
BR %bb.7, implicit-def dead $arguments
|
||||
|
||||
bb.3 (landing-pad):
|
||||
; predecessors: %bb.2
|
||||
successors: %bb.4, %bb.6
|
||||
liveins: $value_stack
|
||||
CATCH_ALL implicit-def $arguments
|
||||
BR_IF %bb.4, %43:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack
|
||||
BR %bb.6, implicit-def $arguments
|
||||
|
||||
bb.4:
|
||||
; predecessors: %bb.3
|
||||
successors: %bb.5, %bb.10
|
||||
liveins: $value_stack
|
||||
BR %bb.5, implicit-def dead $arguments
|
||||
|
||||
bb.5:
|
||||
; predecessors: %bb.4
|
||||
successors: %bb.7(0x80000000); %bb.7(200.00%)
|
||||
liveins: $value_stack
|
||||
CATCHRET %bb.7, %bb.1, implicit-def dead $arguments
|
||||
|
||||
bb.6:
|
||||
; predecessors: %bb.3
|
||||
successors: %bb.12, %bb.11
|
||||
liveins: $value_stack
|
||||
BR %bb.12, implicit-def dead $arguments
|
||||
|
||||
bb.7:
|
||||
; predecessors: %bb.2, %bb.5
|
||||
successors: %bb.9(0x80000000); %bb.9(200.00%)
|
||||
liveins: $value_stack
|
||||
CATCHRET %bb.9, %bb.0, implicit-def dead $arguments
|
||||
|
||||
bb.8:
|
||||
; predecessors: %bb.1
|
||||
liveins: $value_stack
|
||||
UNREACHABLE implicit-def $arguments
|
||||
|
||||
bb.9:
|
||||
; predecessors: %bb.0, %bb.7
|
||||
liveins: $value_stack
|
||||
RETURN_VOID implicit-def $arguments
|
||||
|
||||
bb.10 (landing-pad):
|
||||
; predecessors: %bb.4
|
||||
successors: %bb.11
|
||||
liveins: $value_stack
|
||||
CATCH_ALL implicit-def $arguments
|
||||
CLEANUPRET implicit-def dead $arguments
|
||||
|
||||
bb.11 (landing-pad):
|
||||
; predecessors: %bb.2, %bb.6, %bb.10
|
||||
liveins: $value_stack
|
||||
CATCH_ALL implicit-def $arguments
|
||||
CLEANUPRET implicit-def dead $arguments
|
||||
|
||||
bb.12:
|
||||
; predecessors: %bb.6
|
||||
liveins: $value_stack
|
||||
UNREACHABLE implicit-def $arguments
|
||||
)MIR";
|
||||
|
||||
LLVMContext Context;
|
||||
std::unique_ptr<MIRParser> MIR;
|
||||
MachineModuleInfo MMI(TM.get());
|
||||
std::unique_ptr<Module> M =
|
||||
parseMIR(Context, MIR, *TM, MIRString, "test1", MMI);
|
||||
ASSERT_TRUE(M);
|
||||
|
||||
Function *F = M->getFunction("test1");
|
||||
auto *MF = MMI.getMachineFunction(*F);
|
||||
ASSERT_TRUE(MF);
|
||||
|
||||
WebAssemblyExceptionInfo WEI;
|
||||
MachineDominatorTree MDT;
|
||||
MachineDominanceFrontier MDF;
|
||||
MDT.runOnMachineFunction(*MF);
|
||||
MDF.getBase().analyze(MDT.getBase());
|
||||
WEI.recalculate(MDT, MDF);
|
||||
|
||||
// Exception info structure:
|
||||
// |- bb1 (ehpad), bb2, bb3, bb4, bb5, bb6, bb7, bb8, bb10, bb11, bb12
|
||||
// |- bb3 (ehpad), bb4, bb5, bb6, bb10, bb12
|
||||
// |- bb10 (ehpad)
|
||||
// |- bb11 (ehpad)
|
||||
|
||||
auto *MBB1 = MF->getBlockNumbered(1);
|
||||
auto *WE0 = WEI.getExceptionFor(MBB1);
|
||||
ASSERT_TRUE(WE0);
|
||||
EXPECT_EQ(WE0->getEHPad(), MBB1);
|
||||
EXPECT_EQ(WE0->getParentException(), nullptr);
|
||||
EXPECT_EQ(WE0->getExceptionDepth(), (unsigned)1);
|
||||
|
||||
auto *MBB2 = MF->getBlockNumbered(2);
|
||||
WE0 = WEI.getExceptionFor(MBB2);
|
||||
ASSERT_TRUE(WE0);
|
||||
EXPECT_EQ(WE0->getEHPad(), MBB1);
|
||||
|
||||
auto *MBB7 = MF->getBlockNumbered(7);
|
||||
WE0 = WEI.getExceptionFor(MBB7);
|
||||
ASSERT_TRUE(WE0);
|
||||
EXPECT_EQ(WE0->getEHPad(), MBB1);
|
||||
|
||||
auto *MBB8 = MF->getBlockNumbered(8);
|
||||
WE0 = WEI.getExceptionFor(MBB8);
|
||||
ASSERT_TRUE(WE0);
|
||||
EXPECT_EQ(WE0->getEHPad(), MBB1);
|
||||
|
||||
auto *MBB3 = MF->getBlockNumbered(3);
|
||||
auto *WE0_0 = WEI.getExceptionFor(MBB3);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
EXPECT_EQ(WE0_0->getParentException(), WE0);
|
||||
EXPECT_EQ(WE0_0->getExceptionDepth(), (unsigned)2);
|
||||
|
||||
auto *MBB4 = MF->getBlockNumbered(4);
|
||||
WE0_0 = WEI.getExceptionFor(MBB4);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
|
||||
auto *MBB5 = MF->getBlockNumbered(5);
|
||||
WE0_0 = WEI.getExceptionFor(MBB5);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
|
||||
auto *MBB6 = MF->getBlockNumbered(6);
|
||||
WE0_0 = WEI.getExceptionFor(MBB6);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
|
||||
auto *MBB12 = MF->getBlockNumbered(12);
|
||||
WE0_0 = WEI.getExceptionFor(MBB12);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB3);
|
||||
|
||||
auto *MBB10 = MF->getBlockNumbered(10);
|
||||
auto *WE0_0_0 = WEI.getExceptionFor(MBB10);
|
||||
ASSERT_TRUE(WE0_0_0);
|
||||
EXPECT_EQ(WE0_0_0->getEHPad(), MBB10);
|
||||
EXPECT_EQ(WE0_0_0->getParentException(), WE0_0);
|
||||
EXPECT_EQ(WE0_0_0->getExceptionDepth(), (unsigned)3);
|
||||
|
||||
auto *MBB11 = MF->getBlockNumbered(11);
|
||||
auto *WE0_1 = WEI.getExceptionFor(MBB11);
|
||||
ASSERT_TRUE(WE0_1);
|
||||
EXPECT_EQ(WE0_1->getEHPad(), MBB11);
|
||||
EXPECT_EQ(WE0_1->getParentException(), WE0);
|
||||
EXPECT_EQ(WE0_1->getExceptionDepth(), (unsigned)2);
|
||||
}
|
||||
|
||||
// Terminate pad test
|
||||
TEST(WebAssemblyExceptionInfoTest, TEST2) {
|
||||
std::unique_ptr<TargetMachine> TM = createTargetMachine();
|
||||
ASSERT_TRUE(TM);
|
||||
|
||||
StringRef MIRString = R"MIR(
|
||||
--- |
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32-unknown-unknown"
|
||||
|
||||
declare i32 @__gxx_wasm_personality_v0(...)
|
||||
declare void @_ZSt9terminatev()
|
||||
declare void @__clang_call_terminate(i8*)
|
||||
|
||||
define hidden void @test2() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
|
||||
unreachable
|
||||
}
|
||||
|
||||
...
|
||||
---
|
||||
name: test2
|
||||
liveins:
|
||||
- { reg: '$arguments' }
|
||||
- { reg: '$value_stack' }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.3, %bb.1
|
||||
BR %bb.3, implicit-def dead $arguments
|
||||
|
||||
bb.1 (landing-pad):
|
||||
; predecessors: %bb.0
|
||||
successors: %bb.2, %bb.4
|
||||
%3:i32 = CATCH_I32 0, implicit-def dead $arguments
|
||||
BR %bb.2, implicit-def dead $arguments
|
||||
|
||||
bb.2:
|
||||
; predecessors: %bb.1
|
||||
successors: %bb.3(0x80000000); %bb.3(200.00%)
|
||||
CATCHRET %bb.3, %bb.0, implicit-def dead $arguments
|
||||
|
||||
bb.3:
|
||||
; predecessors: %bb.0, %bb.2
|
||||
RETURN_VOID implicit-def $arguments
|
||||
|
||||
bb.4 (landing-pad):
|
||||
; predecessors: %bb.1
|
||||
successors: %bb.5, %bb.6
|
||||
CATCH_ALL implicit-def $arguments
|
||||
BR %bb.5, implicit-def dead $arguments
|
||||
|
||||
bb.5:
|
||||
; predecessors: %bb.4
|
||||
CLEANUPRET implicit-def dead $arguments
|
||||
|
||||
bb.6 (landing-pad):
|
||||
; predecessors: %bb.4
|
||||
successors: %bb.7(0x80000000); %bb.7(200.00%)
|
||||
%6:i32 = CATCH_I32 0, implicit-def dead $arguments
|
||||
CALL_VOID @__clang_call_terminate, %7:i32, implicit-def $arguments
|
||||
UNREACHABLE implicit-def $arguments
|
||||
|
||||
bb.7 (landing-pad):
|
||||
; predecessors: %bb.6
|
||||
CATCH_ALL implicit-def $arguments
|
||||
CALL_VOID @_ZSt9terminatev, implicit-def $arguments
|
||||
UNREACHABLE implicit-def $arguments
|
||||
)MIR";
|
||||
|
||||
LLVMContext Context;
|
||||
std::unique_ptr<MIRParser> MIR;
|
||||
MachineModuleInfo MMI(TM.get());
|
||||
std::unique_ptr<Module> M =
|
||||
parseMIR(Context, MIR, *TM, MIRString, "test2", MMI);
|
||||
ASSERT_TRUE(M);
|
||||
|
||||
Function *F = M->getFunction("test2");
|
||||
auto *MF = MMI.getMachineFunction(*F);
|
||||
ASSERT_TRUE(MF);
|
||||
|
||||
WebAssemblyExceptionInfo WEI;
|
||||
MachineDominatorTree MDT;
|
||||
MachineDominanceFrontier MDF;
|
||||
MDT.runOnMachineFunction(*MF);
|
||||
MDF.getBase().analyze(MDT.getBase());
|
||||
WEI.recalculate(MDT, MDF);
|
||||
|
||||
// Exception info structure:
|
||||
// |- bb1 (ehpad), bb2, bb4, bb5, bb6, bb7
|
||||
// |- bb4 (ehpad), bb5, bb6, bb7
|
||||
// |- bb6 (ehpad), bb7
|
||||
//
|
||||
// Here, bb6 is a terminate pad with a 'catch' instruction, and bb7 is a
|
||||
// terminate pad with a 'catch_all' instruction, In this case we put bb6 and
|
||||
// bb7 into one exception.
|
||||
|
||||
auto *MBB1 = MF->getBlockNumbered(1);
|
||||
auto *WE0 = WEI.getExceptionFor(MBB1);
|
||||
ASSERT_TRUE(WE0);
|
||||
EXPECT_EQ(WE0->getEHPad(), MBB1);
|
||||
EXPECT_EQ(WE0->getParentException(), nullptr);
|
||||
EXPECT_EQ(WE0->getExceptionDepth(), (unsigned)1);
|
||||
|
||||
auto *MBB2 = MF->getBlockNumbered(2);
|
||||
WE0 = WEI.getExceptionFor(MBB2);
|
||||
ASSERT_TRUE(WE0);
|
||||
EXPECT_EQ(WE0->getEHPad(), MBB1);
|
||||
|
||||
auto *MBB4 = MF->getBlockNumbered(4);
|
||||
auto *WE0_0 = WEI.getExceptionFor(MBB4);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB4);
|
||||
EXPECT_EQ(WE0_0->getParentException(), WE0);
|
||||
EXPECT_EQ(WE0_0->getExceptionDepth(), (unsigned)2);
|
||||
|
||||
auto *MBB5 = MF->getBlockNumbered(5);
|
||||
WE0_0 = WEI.getExceptionFor(MBB5);
|
||||
ASSERT_TRUE(WE0_0);
|
||||
EXPECT_EQ(WE0_0->getEHPad(), MBB4);
|
||||
|
||||
auto *MBB6 = MF->getBlockNumbered(6);
|
||||
auto *WE0_0_0 = WEI.getExceptionFor(MBB6);
|
||||
ASSERT_TRUE(WE0_0_0);
|
||||
EXPECT_EQ(WE0_0_0->getEHPad(), MBB6);
|
||||
EXPECT_EQ(WE0_0_0->getParentException(), WE0_0);
|
||||
EXPECT_EQ(WE0_0_0->getExceptionDepth(), (unsigned)3);
|
||||
|
||||
auto *MBB7 = MF->getBlockNumbered(7);
|
||||
WE0_0_0 = WEI.getExceptionFor(MBB7);
|
||||
ASSERT_TRUE(WE0_0_0);
|
||||
EXPECT_EQ(WE0_0_0->getEHPad(), MBB6);
|
||||
}
|
Loading…
Reference in New Issue
Block a user