440548267b
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}
523 lines
13 KiB
C++
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
|