| // 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 "test/unittests/test-utils.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| #include "src/v8.h" |
| #include "src/wasm/wasm-interpreter.h" |
| |
| #include "test/common/wasm/wasm-macro-gen.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.count(pc) > 0) << "expected control target @" << pc; |
| if (!map.count(pc)) continue; |
| auto& entry = 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, 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.count(pc) == 0) << "expected no control @ +" << pc; |
| } |
| } |
| }; |
| |
| TEST_F(ControlTransferTest, SimpleIf) { |
| byte code[] = { |
| kExprI32Const, // @0 |
| 0, // @1 |
| kExprIf, // @2 |
| kLocalVoid, // @3 |
| kExprEnd // @4 |
| }; |
| CheckTransfers(code, {{2, 2, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, SimpleIf1) { |
| byte code[] = { |
| kExprI32Const, // @0 |
| 0, // @1 |
| kExprIf, // @2 |
| kLocalVoid, // @3 |
| kExprNop, // @4 |
| kExprEnd // @5 |
| }; |
| CheckTransfers(code, {{2, 3, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, SimpleIf2) { |
| byte code[] = { |
| kExprI32Const, // @0 |
| 0, // @1 |
| kExprIf, // @2 |
| kLocalVoid, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @3 |
| kExprBr, // @4 |
| 0, // @5 |
| kExprElse, // @6 |
| kExprEnd // @7 |
| }; |
| CheckTransfers(code, {{2, 5, 0, 0}, {4, 4, 0, 0}, {6, 2, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, IfElseBr) { |
| byte code[] = { |
| kExprI32Const, // @0 |
| 0, // @1 |
| kExprIf, // @2 |
| kLocalVoid, // @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 |
| kLocalVoid, // @1 |
| kExprEnd // @2 |
| }; |
| CheckTransfers(code, {}); |
| } |
| |
| TEST_F(ControlTransferTest, Br0) { |
| byte code[] = { |
| kExprBlock, // @0 |
| kLocalVoid, // @1 |
| kExprBr, // @2 |
| 0, // @3 |
| kExprEnd // @4 |
| }; |
| CheckTransfers(code, {{2, 3, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, Br1) { |
| byte code[] = { |
| kExprBlock, // @0 |
| kLocalVoid, // @1 |
| kExprNop, // @2 |
| kExprBr, // @3 |
| 0, // @4 |
| kExprEnd // @5 |
| }; |
| CheckTransfers(code, {{3, 3, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, Br_v1a) { |
| byte code[] = { |
| kExprBlock, // @0 |
| kLocalVoid, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @3 |
| kExprBr, // @4 |
| 0, // @5 |
| kExprEnd // @6 |
| }; |
| CheckTransfers(code, {{4, 3, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, Br_v1d) { |
| byte code[] = { |
| kExprBlock, // @0 |
| kLocalI32, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @1 |
| kExprBr, // @2 |
| 0, // @3 |
| kExprNop, // @4 |
| kExprEnd // @5 |
| }; |
| CheckTransfers(code, {{2, 4, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, Br0c) { |
| byte code[] = { |
| kExprBlock, // @0 |
| kLocalVoid, // @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 |
| kLocalVoid, // @1 |
| kExprBr, // @2 |
| 0, // @3 |
| kExprEnd // @4 |
| }; |
| CheckTransfers(code, {{2, -2, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, SimpleLoop2) { |
| byte code[] = { |
| kExprLoop, // @0 |
| kLocalVoid, // @1 |
| kExprNop, // @2 |
| kExprBr, // @3 |
| 0, // @4 |
| kExprEnd // @5 |
| }; |
| CheckTransfers(code, {{3, -3, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, SimpleLoopExit1) { |
| byte code[] = { |
| kExprLoop, // @0 |
| kLocalVoid, // @1 |
| kExprBr, // @2 |
| 1, // @3 |
| kExprEnd // @4 |
| }; |
| CheckTransfers(code, {{2, 4, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, SimpleLoopExit2) { |
| byte code[] = { |
| kExprLoop, // @0 |
| kLocalVoid, // @1 |
| kExprNop, // @2 |
| kExprBr, // @3 |
| 1, // @4 |
| kExprEnd // @5 |
| }; |
| CheckTransfers(code, {{3, 4, 0, 0}}); |
| } |
| |
| TEST_F(ControlTransferTest, BrTable0) { |
| byte code[] = { |
| kExprBlock, // @0 |
| kLocalVoid, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @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 |
| kLocalVoid, // @1 |
| kExprBlock, // @2 |
| kLocalVoid, // @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 |
| kLocalI32, // @1 |
| kExprI32Const, // @2 |
| 0, // @3 |
| kExprBlock, // @4 |
| kLocalVoid, // @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 |
| kLocalVoid, // @1 |
| kExprBr, // @2 |
| 0, // @3 |
| kExprBr, // @4 -- no control transfer entry! |
| 1, // @5 |
| kExprEnd, // @6 |
| kExprBlock, // @7 |
| kLocalVoid, // @8 |
| kExprUnreachable, // @9 |
| kExprI32Const, // @10 |
| 0, // @11 |
| kExprIf, // @12 -- no control transfer entry! |
| kLocalVoid, // @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 |