// 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 { #define B1(a) kExprBlock, a, kExprEnd #define B2(a, b) kExprBlock, a, b, kExprEnd #define B3(a, b, c) kExprBlock, a, b, c, kExprEnd #define TRANSFER_VOID 0 #define TRANSFER_ONE 1 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 { 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 void CheckTransfers( const byte (&code)[code_len], std::initializer_list 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 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