1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-23 03:02:36 +01:00
llvm-mirror/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp
Heejin Ahn 5e290d0f9b [WebAssembly] Fix incorrect grouping and sorting of exceptions
This CL is not big but contains changes that span multiple analyses and
passes. This description is very long because it tries to explain basics
on what each pass/analysis does and why we need this change on top of
that. Please feel free to skip parts that are not necessary for your
understanding.

---

`WasmEHFuncInfo` contains the mapping of <EH pad, the EH pad's next
unwind destination>. The value (unwind dest) here is where an exception
should end up when it is not caught by the key (EH pad). We record this
info in WasmEHPrepare to fix catch mismatches, because the CFG itself
does not have this info. A CFG only contains BBs and
predecessor-successor relationship between them, but in `WasmEHFuncInfo`
the unwind destination BB is not necessarily a successor or the key EH
pad BB. Their relationship can be intuitively explained by this C++ code
snippet:
```
try {
  try {
    foo();
  } catch (int) { // EH pad
    ...
  }
} catch (...) {   // unwind destination
}
```
So when `foo()` throws, it goes to `catch (int)` first. But if it is not
caught by it, it ends up in the next unwind destination `catch (...)`.
This unwind destination is what you see in `catchswitch`'s
`unwind label %bb` part.

---

`WebAssemblyExceptionInfo` groups exceptions so that they can be sorted
continuously together in CFGSort, as we do for loops. What this analysis
does is very simple: it creates a single `WebAssemblyException` per EH
pad, and all BBs that are dominated by that EH pad are included in this
exception. We also identify subexception relationship in this way: if
EHPad A domiantes EHPad B, EHPad B's exception is a subexception of
EHPad A's exception.

This simple rule turns out to be incorrect in some cases. In
`WasmEHFuncInfo`, if EHPad A's unwind destination is EHPad B, it means
semantically EHPad B should not be included in EHPad A's exception,
because it does not make sense to rethrow/delegate to an inner scope.
This is what happened in CFGStackify as a result of this:
```
try
  try
  catch
    ...   <- %dest_bb is among here!
  end
delegate %dest_bb
```

So this patch adds a phase in `WebAssemblyExceptionInfo::recalculate` to
make sure excptions' unwind destinations are not subexceptions of
their unwind sources in `WasmEHFuncInfo`.

But this alone does not prevent `dest_bb` in the example above from
being sorted within the inner `catch`'s exception, even if its exception
is not a subexception of that `catch`'s exception anymore, because of
how CFGSort works, which will be explained below.

---

CFGSort places BBs within the same `SortRegion` (loop or exception)
continuously together so they can be demarcated with `loop`-`end_loop`
or `catch`-`end_try` in CFGStackify.

`SortRegion` is a wrapper for one of `MachineLoop` or
`WebAssemblyException`. `SortRegionInfo` already does some complicated
things because there discrepancies between those two data structures.
`WebAssemblyException` is what we control, and it is defined as an EH
pad as its header and BBs dominated by the header as its BBs (with a
newly added exception of unwind destinations explained in the previous
paragraph). But `MachineLoop` is an LLVM data structure and uses the
standard loop detection algorithm. So by the algorithm, BBs that are 1.
dominated by the loop header and 2. have a path back to its header.
Because of the second condition, many BBs that are dominated by the loop
header are not included in the loop. So BBs that contain `return` or
branches to outside of the loop are not technically included in
`MachineLoop`, but they can be sorted together with the loop with no
problem.

Maybe to relax the condition, in CFGSort, when we are in a `SortRegion`
we allow sorting of not only BBs that belong to the current innermost
region but also BBs that are by the current region header.
(This was written this way from the first version written by Dan, when
only loops existed.) But now, we have cases in exceptions when EHPad B
is the unwind destination for EHPad A, even if EHPad B is dominated by
EHPad A it should not be included in EHPad A's exception, and should not
be sorted within EHPad A.

One way to make things work, at least correctly, is change `dominates`
condition to `contains` condition for `SortRegion` when sorting BBs, but
this will change compilation results for existing non-EH code and I
can't be sure it will not degrade performance or code size. I think it
will degrade performance because it will force many BBs dominated by a
loop, which don't have the path back to the header, to be placed after
the loop and it will likely to create more branches and blocks.

So this does a little hacky check when adding BBs to `Preferred` list:
(`Preferred` list is a ready list. CFGSort maintains ready list in two
priority queues: `Preferred` and `Ready`. I'm not very sure why, but it
was written that way from the beginning. BBs are first added to
`Preferred` list and then some of them are pushed to `Ready` list, so
here we only need to guard condition for `Preferred` list.)

When adding a BB to `Preferred` list, we check if that BB is an unwind
destination of another BB. To do this, this adds the reverse mapping,
`UnwindDestToSrc`, and getter methods to `WasmEHFuncInfo`. And if the BB
is an unwind destination, it checks if the current stack of regions
(`Entries`) contains its source BB by traversing the stack backwards. If
we find its unwind source in there, we add the BB to its `Deferred`
list, to make sure that unwind destination BB is added to `Preferred`
list only after that region with the unwind source BB is sorted and
popped from the stack.

---

This does not contain a new test that crashes because of this bug, but
this fix changes the result for one of existing test case. This test
case didn't crash because it fortunately didn't contain `delegate` to
the incorrectly placed unwind destination BB.

Fixes https://github.com/emscripten-core/emscripten/issues/13514.

Reviewed By: dschuff, tlively

Differential Revision: https://reviews.llvm.org/D97247
2021-02-23 14:54:55 -08:00

418 lines
12 KiB
C++

//=== WebAssemblyExceptionInfoTest.cpp - WebAssemblyExceptionInfo unit tests =//
//
// 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
//
//===----------------------------------------------------------------------===//
#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<LLVMTargetMachine> 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<LLVMTargetMachine>(static_cast<LLVMTargetMachine*>(
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<LLVMTargetMachine> 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 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
RETHROW 0, implicit-def dead $arguments
bb.3 (landing-pad):
; predecessors: %bb.2
successors: %bb.4, %bb.6
liveins: $value_stack
%1:i32 = CATCH &__cpp_exception, 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
BR %bb.7, 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 implicit-def $arguments
bb.8 (landing-pad):
; predecessors: %bb.4
successors: %bb.9
liveins: $value_stack
CATCH_ALL implicit-def $arguments
RETHROW 0, implicit-def dead $arguments
bb.9 (landing-pad):
; predecessors: %bb.2, %bb.6, %bb.8
liveins: $value_stack
CATCH_ALL implicit-def $arguments
RETHROW 0, 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(*MF, 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<LLVMTargetMachine> 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 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
%0:i32 = CATCH &__cpp_exception, implicit-def $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
%1:i32 = CATCH &__cpp_exception, 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
BR %bb.7, 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
BR %bb.9, 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 implicit-def $arguments
bb.10 (landing-pad):
; predecessors: %bb.4
successors: %bb.11
liveins: $value_stack
CATCH_ALL implicit-def $arguments
RETHROW 0, implicit-def dead $arguments
bb.11 (landing-pad):
; predecessors: %bb.2, %bb.6, %bb.10
liveins: $value_stack
CATCH_ALL implicit-def $arguments
RETHROW 0, 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(*MF, 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);
}