// Copyright 2017 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/wasm/wasm-code-manager.h" namespace v8 { namespace internal { namespace wasm { namespace wasm_heap_unittest { class DisjointAllocationPoolTest : public ::testing::Test { public: Address A(size_t n) { return reinterpret_cast
(n); } void CheckLooksLike(const DisjointAllocationPool& mem, std::vector> expectation); DisjointAllocationPool Make(std::vector> model); }; void DisjointAllocationPoolTest::CheckLooksLike( const DisjointAllocationPool& mem, std::vector> expectation) { const auto& ranges = mem.ranges(); CHECK_EQ(ranges.size(), expectation.size()); auto iter = expectation.begin(); for (auto it = ranges.begin(), e = ranges.end(); it != e; ++it, ++iter) { CHECK_EQ(it->first, A(iter->first)); CHECK_EQ(it->second, A(iter->second)); } } DisjointAllocationPool DisjointAllocationPoolTest::Make( std::vector> model) { DisjointAllocationPool ret; for (auto& pair : model) { ret.Merge(DisjointAllocationPool(A(pair.first), A(pair.second))); } return ret; } TEST_F(DisjointAllocationPoolTest, Construct) { DisjointAllocationPool a; CHECK(a.IsEmpty()); CHECK_EQ(a.ranges().size(), 0); DisjointAllocationPool b = Make({{1, 5}}); CHECK(!b.IsEmpty()); CHECK_EQ(b.ranges().size(), 1); a.Merge(std::move(b)); CheckLooksLike(a, {{1, 5}}); DisjointAllocationPool c; a.Merge(std::move(c)); CheckLooksLike(a, {{1, 5}}); DisjointAllocationPool e, f; e.Merge(std::move(f)); CHECK(e.IsEmpty()); } TEST_F(DisjointAllocationPoolTest, SimpleExtract) { DisjointAllocationPool a = Make({{1, 5}}); DisjointAllocationPool b = a.AllocatePool(2); CheckLooksLike(a, {{3, 5}}); CheckLooksLike(b, {{1, 3}}); a.Merge(std::move(b)); CheckLooksLike(a, {{1, 5}}); CHECK_EQ(a.ranges().size(), 1); CHECK_EQ(a.ranges().front().first, A(1)); CHECK_EQ(a.ranges().front().second, A(5)); } TEST_F(DisjointAllocationPoolTest, ExtractAll) { DisjointAllocationPool a(A(1), A(5)); DisjointAllocationPool b = a.AllocatePool(4); CheckLooksLike(b, {{1, 5}}); CHECK(a.IsEmpty()); a.Merge(std::move(b)); CheckLooksLike(a, {{1, 5}}); } TEST_F(DisjointAllocationPoolTest, ExtractAccross) { DisjointAllocationPool a = Make({{1, 5}, {10, 20}}); DisjointAllocationPool b = a.AllocatePool(5); CheckLooksLike(a, {{11, 20}}); CheckLooksLike(b, {{1, 5}, {10, 11}}); a.Merge(std::move(b)); CheckLooksLike(a, {{1, 5}, {10, 20}}); } TEST_F(DisjointAllocationPoolTest, ReassembleOutOfOrder) { DisjointAllocationPool a = Make({{1, 5}, {10, 15}}); DisjointAllocationPool b = Make({{7, 8}, {20, 22}}); a.Merge(std::move(b)); CheckLooksLike(a, {{1, 5}, {7, 8}, {10, 15}, {20, 22}}); DisjointAllocationPool c = Make({{1, 5}, {10, 15}}); DisjointAllocationPool d = Make({{7, 8}, {20, 22}}); d.Merge(std::move(c)); CheckLooksLike(d, {{1, 5}, {7, 8}, {10, 15}, {20, 22}}); } TEST_F(DisjointAllocationPoolTest, FailToExtract) { DisjointAllocationPool a = Make({{1, 5}}); DisjointAllocationPool b = a.AllocatePool(5); CheckLooksLike(a, {{1, 5}}); CHECK(b.IsEmpty()); } TEST_F(DisjointAllocationPoolTest, FailToExtractExact) { DisjointAllocationPool a = Make({{1, 5}, {10, 14}}); DisjointAllocationPool b = a.Allocate(5); CheckLooksLike(a, {{1, 5}, {10, 14}}); CHECK(b.IsEmpty()); } TEST_F(DisjointAllocationPoolTest, ExtractExact) { DisjointAllocationPool a = Make({{1, 5}, {10, 15}}); DisjointAllocationPool b = a.Allocate(5); CheckLooksLike(a, {{1, 5}}); CheckLooksLike(b, {{10, 15}}); } TEST_F(DisjointAllocationPoolTest, Merging) { DisjointAllocationPool a = Make({{10, 15}, {20, 25}}); a.Merge(Make({{15, 20}})); CheckLooksLike(a, {{10, 25}}); } TEST_F(DisjointAllocationPoolTest, MergingMore) { DisjointAllocationPool a = Make({{10, 15}, {20, 25}, {30, 35}}); a.Merge(Make({{15, 20}, {25, 30}})); CheckLooksLike(a, {{10, 35}}); } TEST_F(DisjointAllocationPoolTest, MergingSkip) { DisjointAllocationPool a = Make({{10, 15}, {20, 25}, {30, 35}}); a.Merge(Make({{25, 30}})); CheckLooksLike(a, {{10, 15}, {20, 35}}); } TEST_F(DisjointAllocationPoolTest, MergingSkipLargerSrc) { DisjointAllocationPool a = Make({{10, 15}, {20, 25}, {30, 35}}); a.Merge(Make({{25, 30}, {35, 40}})); CheckLooksLike(a, {{10, 15}, {20, 40}}); } TEST_F(DisjointAllocationPoolTest, MergingSkipLargerSrcWithGap) { DisjointAllocationPool a = Make({{10, 15}, {20, 25}, {30, 35}}); a.Merge(Make({{25, 30}, {36, 40}})); CheckLooksLike(a, {{10, 15}, {20, 35}, {36, 40}}); } class WasmCodeManagerTest : public TestWithIsolate { public: using NativeModulePtr = std::unique_ptr; enum ModuleStyle : int { Fixed = 0, Growable = 1 }; const std::vector styles() const { return std::vector({Fixed, Growable}); } // We pretend all our modules have 10 functions and no imports, just so // we can size up the code_table. NativeModulePtr AllocFixedModule(WasmCodeManager* manager, size_t size) { return manager->NewNativeModule(size, 10, 0, false); } NativeModulePtr AllocGrowableModule(WasmCodeManager* manager, size_t size) { return manager->NewNativeModule(size, 10, 0, true); } NativeModulePtr AllocModule(WasmCodeManager* manager, size_t size, ModuleStyle style) { switch (style) { case Fixed: return AllocFixedModule(manager, size); case Growable: return AllocGrowableModule(manager, size); default: UNREACHABLE(); } } WasmCode* AddCode(NativeModule* native_module, uint32_t index, size_t size) { CodeDesc desc; memset(reinterpret_cast(&desc), 0, sizeof(CodeDesc)); std::unique_ptr exec_buff(new byte[size]); desc.buffer = exec_buff.get(); desc.instr_size = static_cast(size); return native_module->AddCode(desc, 0, index, 0, {}, false); } size_t page() const { return base::OS::AllocatePageSize(); } v8::Isolate* v8_isolate() const { return reinterpret_cast(isolate()); } }; TEST_F(WasmCodeManagerTest, EmptyCase) { for (auto style : styles()) { WasmCodeManager manager(v8_isolate(), 0 * page()); CHECK_EQ(0, manager.remaining_uncommitted()); NativeModulePtr native_module = AllocModule(&manager, 1 * page(), style); CHECK(native_module); WasmCode* code = AddCode(native_module.get(), 0, 10); CHECK_NULL(code); CHECK_EQ(0, manager.remaining_uncommitted()); native_module.reset(); CHECK_EQ(0, manager.remaining_uncommitted()); } } TEST_F(WasmCodeManagerTest, AllocateAndGoOverLimit) { for (auto style : styles()) { WasmCodeManager manager(v8_isolate(), 1 * page()); CHECK_EQ(1 * page(), manager.remaining_uncommitted()); NativeModulePtr native_module = AllocModule(&manager, 1 * page(), style); CHECK(native_module); CHECK_EQ(1 * page(), manager.remaining_uncommitted()); uint32_t index = 0; WasmCode* code = AddCode(native_module.get(), index++, 1 * kCodeAlignment); CHECK_NOT_NULL(code); CHECK_EQ(0, manager.remaining_uncommitted()); code = AddCode(native_module.get(), index++, 3 * kCodeAlignment); CHECK_NOT_NULL(code); CHECK_EQ(0, manager.remaining_uncommitted()); code = AddCode(native_module.get(), index++, page() - 4 * kCodeAlignment); CHECK_NOT_NULL(code); CHECK_EQ(0, manager.remaining_uncommitted()); code = AddCode(native_module.get(), index++, 1 * kCodeAlignment); CHECK_NULL(code); CHECK_EQ(0, manager.remaining_uncommitted()); native_module.reset(); CHECK_EQ(1 * page(), manager.remaining_uncommitted()); } } TEST_F(WasmCodeManagerTest, TotalLimitIrrespectiveOfModuleCount) { for (auto style : styles()) { WasmCodeManager manager(v8_isolate(), 1 * page()); NativeModulePtr nm1 = AllocModule(&manager, 1 * page(), style); NativeModulePtr nm2 = AllocModule(&manager, 1 * page(), style); CHECK(nm1); CHECK(nm2); WasmCode* code = AddCode(nm1.get(), 0, 1 * page()); CHECK_NOT_NULL(code); code = AddCode(nm2.get(), 0, 1 * page()); CHECK_NULL(code); } } TEST_F(WasmCodeManagerTest, DifferentHeapsApplyLimitsIndependently) { for (auto style : styles()) { WasmCodeManager manager1(v8_isolate(), 1 * page()); WasmCodeManager manager2(v8_isolate(), 2 * page()); NativeModulePtr nm1 = AllocModule(&manager1, 1 * page(), style); NativeModulePtr nm2 = AllocModule(&manager2, 1 * page(), style); CHECK(nm1); CHECK(nm2); WasmCode* code = AddCode(nm1.get(), 0, 1 * page()); CHECK_NOT_NULL(code); CHECK_EQ(0, manager1.remaining_uncommitted()); code = AddCode(nm2.get(), 0, 1 * page()); CHECK_NOT_NULL(code); } } TEST_F(WasmCodeManagerTest, GrowingVsFixedModule) { for (auto style : styles()) { WasmCodeManager manager(v8_isolate(), 3 * page()); NativeModulePtr nm = AllocModule(&manager, 1 * page(), style); WasmCode* code = AddCode(nm.get(), 0, 1 * page() + kCodeAlignment); if (style == Fixed) { CHECK_NULL(code); CHECK_EQ(manager.remaining_uncommitted(), 3 * page()); } else { CHECK_NOT_NULL(code); CHECK_EQ(manager.remaining_uncommitted(), 1 * page()); } } } TEST_F(WasmCodeManagerTest, CommitIncrements) { for (auto style : styles()) { WasmCodeManager manager(v8_isolate(), 10 * page()); NativeModulePtr nm = AllocModule(&manager, 3 * page(), style); WasmCode* code = AddCode(nm.get(), 0, kCodeAlignment); CHECK_NOT_NULL(code); CHECK_EQ(manager.remaining_uncommitted(), 9 * page()); code = AddCode(nm.get(), 1, 2 * page()); CHECK_NOT_NULL(code); CHECK_EQ(manager.remaining_uncommitted(), 7 * page()); code = AddCode(nm.get(), 2, page() - kCodeAlignment); CHECK_NOT_NULL(code); CHECK_EQ(manager.remaining_uncommitted(), 7 * page()); } } TEST_F(WasmCodeManagerTest, Lookup) { for (auto style : styles()) { WasmCodeManager manager(v8_isolate(), 2 * page()); NativeModulePtr nm1 = AllocModule(&manager, 1 * page(), style); NativeModulePtr nm2 = AllocModule(&manager, 1 * page(), style); WasmCode* code1_0 = AddCode(nm1.get(), 0, kCodeAlignment); CHECK_EQ(nm1.get(), code1_0->owner()); WasmCode* code1_1 = AddCode(nm1.get(), 1, kCodeAlignment); WasmCode* code2_0 = AddCode(nm2.get(), 0, kCodeAlignment); WasmCode* code2_1 = AddCode(nm2.get(), 1, kCodeAlignment); CHECK_EQ(nm2.get(), code2_1->owner()); CHECK_EQ(0, code1_0->index()); CHECK_EQ(1, code1_1->index()); CHECK_EQ(0, code2_0->index()); CHECK_EQ(1, code2_1->index()); // we know the manager object is allocated here, so we shouldn't // find any WasmCode* associated with that ptr. WasmCode* not_found = manager.LookupCode(reinterpret_cast
(&manager)); CHECK_NULL(not_found); WasmCode* found = manager.LookupCode(code1_0->instructions().start()); CHECK_EQ(found, code1_0); found = manager.LookupCode(code2_1->instructions().start() + (code2_1->instructions().size() / 2)); CHECK_EQ(found, code2_1); found = manager.LookupCode(code2_1->instructions().start() + code2_1->instructions().size() - 1); CHECK_EQ(found, code2_1); found = manager.LookupCode(code2_1->instructions().start() + code2_1->instructions().size()); CHECK_NULL(found); Address mid_code1_1 = code1_1->instructions().start() + (code1_1->instructions().size() / 2); CHECK_EQ(code1_1, manager.LookupCode(mid_code1_1)); nm1.reset(); CHECK_NULL(manager.LookupCode(mid_code1_1)); } } TEST_F(WasmCodeManagerTest, MultiManagerLookup) { for (auto style : styles()) { WasmCodeManager manager1(v8_isolate(), 2 * page()); WasmCodeManager manager2(v8_isolate(), 2 * page()); NativeModulePtr nm1 = AllocModule(&manager1, 1 * page(), style); NativeModulePtr nm2 = AllocModule(&manager2, 1 * page(), style); WasmCode* code1_0 = AddCode(nm1.get(), 0, kCodeAlignment); CHECK_EQ(nm1.get(), code1_0->owner()); WasmCode* code1_1 = AddCode(nm1.get(), 1, kCodeAlignment); WasmCode* code2_0 = AddCode(nm2.get(), 0, kCodeAlignment); WasmCode* code2_1 = AddCode(nm2.get(), 1, kCodeAlignment); CHECK_EQ(nm2.get(), code2_1->owner()); CHECK_EQ(0, code1_0->index()); CHECK_EQ(1, code1_1->index()); CHECK_EQ(0, code2_0->index()); CHECK_EQ(1, code2_1->index()); CHECK_EQ(code1_0, manager1.LookupCode(code1_0->instructions().start())); CHECK_NULL(manager2.LookupCode(code1_0->instructions().start())); } } TEST_F(WasmCodeManagerTest, LookupWorksAfterRewrite) { for (auto style : styles()) { WasmCodeManager manager(v8_isolate(), 2 * page()); NativeModulePtr nm1 = AllocModule(&manager, 1 * page(), style); WasmCode* code0 = AddCode(nm1.get(), 0, kCodeAlignment); WasmCode* code1 = AddCode(nm1.get(), 1, kCodeAlignment); CHECK_EQ(0, code0->index()); CHECK_EQ(1, code1->index()); CHECK_EQ(code1, manager.LookupCode(code1->instructions().start())); WasmCode* code1_1 = AddCode(nm1.get(), 1, kCodeAlignment); CHECK_EQ(1, code1_1->index()); CHECK_EQ(code1, manager.LookupCode(code1->instructions().start())); CHECK_EQ(code1_1, manager.LookupCode(code1_1->instructions().start())); } } } // namespace wasm_heap_unittest } // namespace wasm } // namespace internal } // namespace v8