| // 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<Address>(n); } |
| void CheckLooksLike(const DisjointAllocationPool& mem, |
| std::vector<std::pair<size_t, size_t>> expectation); |
| DisjointAllocationPool Make(std::vector<std::pair<size_t, size_t>> model); |
| }; |
| |
| void DisjointAllocationPoolTest::CheckLooksLike( |
| const DisjointAllocationPool& mem, |
| std::vector<std::pair<size_t, size_t>> 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<std::pair<size_t, size_t>> 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<NativeModule>; |
| enum ModuleStyle : int { Fixed = 0, Growable = 1 }; |
| |
| const std::vector<ModuleStyle> styles() const { |
| return std::vector<ModuleStyle>({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<void*>(&desc), 0, sizeof(CodeDesc)); |
| std::unique_ptr<byte[]> exec_buff(new byte[size]); |
| desc.buffer = exec_buff.get(); |
| desc.instr_size = static_cast<int>(size); |
| return native_module->AddCode(desc, 0, index, 0, {}, false); |
| } |
| |
| size_t page() const { return AllocatePageSize(); } |
| v8::Isolate* v8_isolate() const { |
| return reinterpret_cast<v8::Isolate*>(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<Address>(&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 |