v8/test/unittests/wasm/control-transfer-unittest.cc
Thibaud Michaud 440548267b [wasm][interpreter][eh] Implement catch with immediate
In the latest spec, catch can take an exception index immediate, and
control-flow jumps to the appropriate catch handler depending on the
thrown exception.

Do this by allowing multiple jump targets for the same pc in labels and
in the control transfer map. At runtime, the unwinder will choose the
appropriate control transfer entry based on the exception tag, unpack
the exception and jump to the handler.

Enable the exception cctests that were currently disabled for the
interpreter, fix some issues and add tests for the new behaviors.

R=clemensb@chromium.org

Bug: v8:8091
Change-Id: I30cb8f9459647a7c6f7bfd9785b238a9c9e9fc10
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2690587
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72661}
2021-02-11 15:38:56 +00:00

523 lines
13 KiB
C++

// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/init/v8.h"
#include "test/common/wasm/wasm-interpreter.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"
using testing::MakeMatcher;
using testing::Matcher;
using testing::MatcherInterface;
using testing::MatchResultListener;
using testing::StringMatchResultListener;
namespace v8 {
namespace internal {
namespace wasm {
struct ExpectedControlTransfer {
pc_t pc;
pcdiff_t pc_diff;
uint32_t sp_diff;
uint32_t target_arity;
};
// For nicer error messages.
class ControlTransferMatcher
: public MatcherInterface<const ControlTransferEntry&> {
public:
explicit ControlTransferMatcher(pc_t pc,
const ExpectedControlTransfer& expected)
: pc_(pc), expected_(expected) {}
void DescribeTo(std::ostream* os) const override {
*os << "@" << pc_ << ": pcdiff = " << expected_.pc_diff
<< ", spdiff = " << expected_.sp_diff
<< ", target arity = " << expected_.target_arity;
}
bool MatchAndExplain(const ControlTransferEntry& input,
MatchResultListener* listener) const override {
if (input.pc_diff == expected_.pc_diff &&
input.sp_diff == expected_.sp_diff &&
input.target_arity == expected_.target_arity) {
return true;
}
*listener << "@" << pc_ << ": pcdiff = " << input.pc_diff
<< ", spdiff = " << input.sp_diff
<< ", target arity = " << input.target_arity;
return false;
}
private:
pc_t pc_;
const ExpectedControlTransfer& expected_;
};
class ControlTransferTest : public TestWithZone {
public:
template <int code_len>
void CheckTransfers(
const byte (&code)[code_len],
std::initializer_list<ExpectedControlTransfer> expected_transfers) {
byte code_with_end[code_len + 1]; // NOLINT: code_len is a constant here
memcpy(code_with_end, code, code_len);
code_with_end[code_len] = kExprEnd;
ControlTransferMap map = WasmInterpreter::ComputeControlTransfersForTesting(
zone(), nullptr, code_with_end, code_with_end + code_len + 1);
// Check all control targets in the map.
for (auto& expected_transfer : expected_transfers) {
pc_t pc = expected_transfer.pc;
EXPECT_TRUE(map.map.count(pc) > 0) << "expected control target @" << pc;
if (!map.map.count(pc)) continue;
auto& entry = map.map[pc];
EXPECT_THAT(entry, MakeMatcher(new ControlTransferMatcher(
pc, expected_transfer)));
}
// Check there are no other control targets.
CheckNoOtherTargets(code_with_end, code_with_end + code_len + 1, map,
expected_transfers);
}
void CheckNoOtherTargets(
const byte* start, const byte* end, const ControlTransferMap& map,
std::initializer_list<ExpectedControlTransfer> targets) {
// Check there are no other control targets.
for (pc_t pc = 0; start + pc < end; pc++) {
bool found = false;
for (auto& target : targets) {
if (target.pc == pc) {
found = true;
break;
}
}
if (found) continue;
EXPECT_TRUE(map.map.count(pc) == 0) << "expected no control @ +" << pc;
}
}
};
TEST_F(ControlTransferTest, SimpleIf) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprEnd // @4
};
CheckTransfers(code, {{2, 2, 0, 0}});
}
TEST_F(ControlTransferTest, SimpleIf1) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprNop, // @4
kExprEnd // @5
};
CheckTransfers(code, {{2, 3, 0, 0}});
}
TEST_F(ControlTransferTest, SimpleIf2) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprNop, // @4
kExprNop, // @5
kExprEnd // @6
};
CheckTransfers(code, {{2, 4, 0, 0}});
}
TEST_F(ControlTransferTest, SimpleIfElse) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprElse, // @4
kExprEnd // @5
};
CheckTransfers(code, {{2, 3, 0, 0}, {4, 2, 0, 0}});
}
TEST_F(ControlTransferTest, SimpleIfElse_v1) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprI32Const, // @4
0, // @5
kExprElse, // @6
kExprI32Const, // @7
0, // @8
kExprEnd // @9
};
CheckTransfers(code, {{2, 5, 0, 0}, {6, 4, 1, 0}});
}
TEST_F(ControlTransferTest, SimpleIfElse1) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprElse, // @4
kExprNop, // @5
kExprEnd // @6
};
CheckTransfers(code, {{2, 3, 0, 0}, {4, 3, 0, 0}});
}
TEST_F(ControlTransferTest, IfBr) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprBr, // @4
0, // @5
kExprEnd // @6
};
CheckTransfers(code, {{2, 4, 0, 0}, {4, 3, 0, 0}});
}
TEST_F(ControlTransferTest, IfBrElse) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprBr, // @4
0, // @5
kExprElse, // @6
kExprEnd // @7
};
CheckTransfers(code, {{2, 5, 0, 0}, {4, 4, 0, 0}});
}
TEST_F(ControlTransferTest, IfElseBr) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprIf, // @2
kVoidCode, // @3
kExprElse, // @4
kExprBr, // @5
0, // @6
kExprEnd // @7
};
CheckTransfers(code, {{2, 3, 0, 0}, {4, 4, 0, 0}, {5, 3, 0, 0}});
}
TEST_F(ControlTransferTest, BlockEmpty) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprEnd // @2
};
CheckTransfers(code, {});
}
TEST_F(ControlTransferTest, Br0) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprBr, // @2
0, // @3
kExprEnd // @4
};
CheckTransfers(code, {{2, 3, 0, 0}});
}
TEST_F(ControlTransferTest, Br1) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprNop, // @2
kExprBr, // @3
0, // @4
kExprEnd // @5
};
CheckTransfers(code, {{3, 3, 0, 0}});
}
TEST_F(ControlTransferTest, Br_v1a) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprI32Const, // @2
0, // @3
kExprBr, // @4
0, // @5
kExprEnd // @6
};
CheckTransfers(code, {{4, 3, 1, 0}});
}
TEST_F(ControlTransferTest, Br_v1b) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprI32Const, // @2
0, // @3
kExprBr, // @4
0, // @5
kExprEnd // @6
};
CheckTransfers(code, {{4, 3, 1, 0}});
}
TEST_F(ControlTransferTest, Br_v1c) {
byte code[] = {
kExprI32Const, // @0
0, // @1
kExprBlock, // @2
kVoidCode, // @3
kExprBr, // @4
0, // @5
kExprEnd // @6
};
CheckTransfers(code, {{4, 3, 0, 0}});
}
TEST_F(ControlTransferTest, Br_v1d) {
byte code[] = {
kExprBlock, // @0
kI32Code, // @1
kExprI32Const, // @2
0, // @3
kExprBr, // @4
0, // @5
kExprEnd // @6
};
CheckTransfers(code, {{4, 3, 1, 1}});
}
TEST_F(ControlTransferTest, Br2) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprNop, // @2
kExprNop, // @3
kExprBr, // @4
0, // @5
kExprEnd // @6
};
CheckTransfers(code, {{4, 3, 0, 0}});
}
TEST_F(ControlTransferTest, Br0b) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprBr, // @2
0, // @3
kExprNop, // @4
kExprEnd // @5
};
CheckTransfers(code, {{2, 4, 0, 0}});
}
TEST_F(ControlTransferTest, Br0c) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprBr, // @2
0, // @3
kExprNop, // @4
kExprNop, // @5
kExprEnd // @6
};
CheckTransfers(code, {{2, 5, 0, 0}});
}
TEST_F(ControlTransferTest, SimpleLoop1) {
byte code[] = {
kExprLoop, // @0
kVoidCode, // @1
kExprBr, // @2
0, // @3
kExprEnd // @4
};
CheckTransfers(code, {{2, -2, 0, 0}});
}
TEST_F(ControlTransferTest, SimpleLoop2) {
byte code[] = {
kExprLoop, // @0
kVoidCode, // @1
kExprNop, // @2
kExprBr, // @3
0, // @4
kExprEnd // @5
};
CheckTransfers(code, {{3, -3, 0, 0}});
}
TEST_F(ControlTransferTest, SimpleLoopExit1) {
byte code[] = {
kExprLoop, // @0
kVoidCode, // @1
kExprBr, // @2
1, // @3
kExprEnd // @4
};
CheckTransfers(code, {{2, 4, 0, 0}});
}
TEST_F(ControlTransferTest, SimpleLoopExit2) {
byte code[] = {
kExprLoop, // @0
kVoidCode, // @1
kExprNop, // @2
kExprBr, // @3
1, // @4
kExprEnd // @5
};
CheckTransfers(code, {{3, 4, 0, 0}});
}
TEST_F(ControlTransferTest, BrTable0) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprI32Const, // @2
0, // @3
kExprBrTable, // @4
0, // @5
U32V_1(0), // @6
kExprEnd // @7
};
CheckTransfers(code, {{4, 4, 0, 0}});
}
TEST_F(ControlTransferTest, BrTable0_v1a) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprI32Const, // @2
0, // @3
kExprI32Const, // @4
0, // @5
kExprBrTable, // @6
0, // @7
U32V_1(0), // @8
kExprEnd // @9
};
CheckTransfers(code, {{6, 4, 1, 0}});
}
TEST_F(ControlTransferTest, BrTable0_v1b) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprI32Const, // @2
0, // @3
kExprI32Const, // @4
0, // @5
kExprBrTable, // @6
0, // @7
U32V_1(0), // @8
kExprEnd // @9
};
CheckTransfers(code, {{6, 4, 1, 0}});
}
TEST_F(ControlTransferTest, BrTable1) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprI32Const, // @2
0, // @3
kExprBrTable, // @4
1, // @5
U32V_1(0), // @6
U32V_1(0), // @7
kExprEnd // @8
};
CheckTransfers(code, {{4, 5, 0, 0}, {5, 4, 0, 0}});
}
TEST_F(ControlTransferTest, BrTable2) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprBlock, // @2
kVoidCode, // @3
kExprI32Const, // @4
0, // @5
kExprBrTable, // @6
2, // @7
U32V_1(0), // @8
U32V_1(0), // @9
U32V_1(1), // @10
kExprEnd, // @11
kExprEnd // @12
};
CheckTransfers(code, {{6, 6, 0, 0}, {7, 5, 0, 0}, {8, 5, 0, 0}});
}
TEST_F(ControlTransferTest, BiggerSpDiffs) {
byte code[] = {
kExprBlock, // @0
kI32Code, // @1
kExprI32Const, // @2
0, // @3
kExprBlock, // @4
kVoidCode, // @5
kExprI32Const, // @6
0, // @7
kExprI32Const, // @8
0, // @9
kExprI32Const, // @10
0, // @11
kExprBrIf, // @12
0, // @13
kExprBr, // @14
1, // @15
kExprEnd, // @16
kExprEnd // @17
};
CheckTransfers(code, {{12, 5, 2, 0}, {14, 4, 3, 1}});
}
TEST_F(ControlTransferTest, NoInfoForUnreachableCode) {
byte code[] = {
kExprBlock, // @0
kVoidCode, // @1
kExprBr, // @2
0, // @3
kExprBr, // @4 -- no control transfer entry!
1, // @5
kExprEnd, // @6
kExprBlock, // @7
kVoidCode, // @8
kExprUnreachable, // @9
kExprI32Const, // @10
0, // @11
kExprIf, // @12 -- no control transfer entry!
kVoidCode, // @13
kExprBr, // @14 -- no control transfer entry!
0, // @15
kExprElse, // @16 -- no control transfer entry!
kExprEnd, // @17
kExprEnd // @18
};
CheckTransfers(code, {{2, 5, 0, 0}});
}
} // namespace wasm
} // namespace internal
} // namespace v8