[wasm] A simple allocator datastructure for off-the heap
We'll use this allocator in a follow-up CL to: - allocate speculative sizes of memory for a module that's being compiled (e.g. 2*size of wasm code). - each module will own such a sub-pool, and then use it to allocate contiguous chunks of memory for code. The underlying assumptions for the chosen allocation strategy is that: - the allocation granularity for pools is 1 page, so that no one page is owned by more than one wasm module - typical pool sizes (given module sizes) are multiple pages. - modules and module instances are typically few and long lived. Typically, we expect one module and one instance. This means we shouldn't expect fragmentations that lead to code being non-allocatable, or prohibitively many ranges. The data structure just manages ranges of addresses. Virtual memory management will be separate, as part of the responsibility of a "WasmHeap" that will be introduced in the future. So will concurrency control. Bug: Change-Id: Id99f46d10c25553b013054d994760f3c2a737c39 Reviewed-on: https://chromium-review.googlesource.com/669296 Commit-Queue: Mircea Trofin <mtrofin@chromium.org> Reviewed-by: Eric Holk <eholk@chromium.org> Reviewed-by: Brad Nelson <bradnelson@chromium.org> Cr-Commit-Position: refs/heads/master@{#48053}
This commit is contained in:
parent
258f270f15
commit
110d9ab005
2
BUILD.gn
2
BUILD.gn
@ -2059,6 +2059,8 @@ v8_source_set("v8_base") {
|
||||
"src/wasm/wasm-debug.cc",
|
||||
"src/wasm/wasm-external-refs.cc",
|
||||
"src/wasm/wasm-external-refs.h",
|
||||
"src/wasm/wasm-heap.cc",
|
||||
"src/wasm/wasm-heap.h",
|
||||
"src/wasm/wasm-interpreter.cc",
|
||||
"src/wasm/wasm-interpreter.h",
|
||||
"src/wasm/wasm-js.cc",
|
||||
|
@ -1447,6 +1447,8 @@
|
||||
'wasm/wasm-debug.cc',
|
||||
'wasm/wasm-external-refs.cc',
|
||||
'wasm/wasm-external-refs.h',
|
||||
'wasm/wasm-heap.cc',
|
||||
'wasm/wasm-heap.h',
|
||||
'wasm/wasm-js.cc',
|
||||
'wasm/wasm-js.h',
|
||||
'wasm/wasm-limits.h',
|
||||
|
101
src/wasm/wasm-heap.cc
Normal file
101
src/wasm/wasm-heap.cc
Normal file
@ -0,0 +1,101 @@
|
||||
// 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 "src/wasm/wasm-heap.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
DisjointAllocationPool::DisjointAllocationPool(Address start, Address end) {
|
||||
ranges_.push_back({start, end});
|
||||
}
|
||||
|
||||
void DisjointAllocationPool::Release(DisjointAllocationPool&& other) {
|
||||
auto dest_it = ranges_.begin();
|
||||
auto dest_end = ranges_.end();
|
||||
|
||||
for (auto src_it = other.ranges_.begin(), src_end = other.ranges_.end();
|
||||
src_it != src_end;) {
|
||||
if (dest_it == dest_end) {
|
||||
// everything else coming from src will be inserted
|
||||
// at the back of ranges_ from now on.
|
||||
ranges_.push_back(*src_it);
|
||||
++src_it;
|
||||
continue;
|
||||
}
|
||||
// Before or adjacent to dest. Insert or merge, and advance
|
||||
// just src.
|
||||
if (dest_it->first >= src_it->second) {
|
||||
if (dest_it->first == src_it->second) {
|
||||
dest_it->first = src_it->first;
|
||||
} else {
|
||||
ranges_.insert(dest_it, {src_it->first, src_it->second});
|
||||
}
|
||||
++src_it;
|
||||
continue;
|
||||
}
|
||||
// Src is strictly after dest. Skip over this dest.
|
||||
if (dest_it->second < src_it->first) {
|
||||
++dest_it;
|
||||
continue;
|
||||
}
|
||||
// Src is adjacent from above. Merge and advance
|
||||
// just src, because the next src, if any, is bound to be
|
||||
// strictly above the newly-formed range.
|
||||
DCHECK_EQ(dest_it->second, src_it->first);
|
||||
dest_it->second = src_it->second;
|
||||
++src_it;
|
||||
// Now that we merged, maybe this new range is adjacent to
|
||||
// the next. Since we assume src to have come from the
|
||||
// same original memory pool, it follows that the next src
|
||||
// must be above or adjacent to the new bubble.
|
||||
auto next_dest = dest_it;
|
||||
++next_dest;
|
||||
if (next_dest != dest_end && dest_it->second == next_dest->first) {
|
||||
dest_it->second = next_dest->second;
|
||||
ranges_.erase(next_dest);
|
||||
}
|
||||
|
||||
// src_it points now at the next, if any, src
|
||||
DCHECK_IMPLIES(src_it != src_end, src_it->first >= dest_it->second);
|
||||
}
|
||||
}
|
||||
|
||||
DisjointAllocationPool DisjointAllocationPool::Extract(size_t size,
|
||||
ExtractionMode mode) {
|
||||
DisjointAllocationPool ret;
|
||||
for (auto it = ranges_.begin(), end = ranges_.end(); it != end;) {
|
||||
auto current = it;
|
||||
++it;
|
||||
DCHECK_LT(current->first, current->second);
|
||||
size_t current_size = reinterpret_cast<size_t>(current->second) -
|
||||
reinterpret_cast<size_t>(current->first);
|
||||
if (size == current_size) {
|
||||
ret.ranges_.push_back(*current);
|
||||
ranges_.erase(current);
|
||||
return ret;
|
||||
}
|
||||
if (size < current_size) {
|
||||
ret.ranges_.push_back({current->first, current->first + size});
|
||||
current->first += size;
|
||||
DCHECK(current->first < current->second);
|
||||
return ret;
|
||||
}
|
||||
if (mode != kContiguous) {
|
||||
size -= current_size;
|
||||
ret.ranges_.push_back(*current);
|
||||
ranges_.erase(current);
|
||||
}
|
||||
}
|
||||
if (size > 0) {
|
||||
Release(std::move(ret));
|
||||
return {};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
66
src/wasm/wasm-heap.h
Normal file
66
src/wasm/wasm-heap.h
Normal file
@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
|
||||
#ifndef V8_WASM_HEAP_H_
|
||||
#define V8_WASM_HEAP_H_
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "src/base/macros.h"
|
||||
#include "src/vector.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
// Sorted, disjoint and non-overlapping memory ranges. A range is of the
|
||||
// form [start, end). So there's no [start, end), [end, other_end),
|
||||
// because that should have been reduced to [start, other_end).
|
||||
using AddressRange = std::pair<Address, Address>;
|
||||
class V8_EXPORT_PRIVATE DisjointAllocationPool final {
|
||||
public:
|
||||
enum ExtractionMode : bool { kAny = false, kContiguous = true };
|
||||
DisjointAllocationPool() {}
|
||||
|
||||
explicit DisjointAllocationPool(Address, Address);
|
||||
|
||||
DisjointAllocationPool(DisjointAllocationPool&& other) = default;
|
||||
DisjointAllocationPool& operator=(DisjointAllocationPool&& other) = default;
|
||||
|
||||
// Merge the ranges of the parameter into this object. Ordering is
|
||||
// preserved. The assumption is that the passed parameter is
|
||||
// not intersecting this object - for example, it was obtained
|
||||
// from a previous Allocate{Pool}.
|
||||
void Release(DisjointAllocationPool&&);
|
||||
|
||||
// Allocate a contiguous range of size {size}. Return an empty pool on
|
||||
// failure.
|
||||
DisjointAllocationPool Allocate(size_t size) {
|
||||
return Extract(size, kContiguous);
|
||||
}
|
||||
|
||||
// Allocate a sub-pool of size {size}. Return an empty pool on failure.
|
||||
DisjointAllocationPool AllocatePool(size_t size) {
|
||||
return Extract(size, kAny);
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return ranges_.empty(); }
|
||||
const std::list<AddressRange>& ranges() const { return ranges_; }
|
||||
|
||||
private:
|
||||
// Extract out a total of {size}. By default, the return may
|
||||
// be more than one range. If kContiguous is passed, the return
|
||||
// will be one range. If the operation fails, this object is
|
||||
// unchanged, and the return {IsEmpty()}
|
||||
DisjointAllocationPool Extract(size_t size, ExtractionMode mode);
|
||||
|
||||
std::list<AddressRange> ranges_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DisjointAllocationPool)
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
#endif
|
@ -157,6 +157,7 @@ v8_executable("unittests") {
|
||||
"wasm/loop-assignment-analysis-unittest.cc",
|
||||
"wasm/module-decoder-unittest.cc",
|
||||
"wasm/streaming-decoder-unittest.cc",
|
||||
"wasm/wasm-heap-unittest.cc",
|
||||
"wasm/wasm-macro-gen-unittest.cc",
|
||||
"wasm/wasm-module-builder-unittest.cc",
|
||||
"wasm/wasm-opcodes-unittest.cc",
|
||||
|
@ -154,6 +154,7 @@
|
||||
'wasm/control-transfer-unittest.cc',
|
||||
'wasm/decoder-unittest.cc',
|
||||
'wasm/function-body-decoder-unittest.cc',
|
||||
'wasm/wasm-heap-unittest.cc',
|
||||
'wasm/leb-helper-unittest.cc',
|
||||
'wasm/loop-assignment-analysis-unittest.cc',
|
||||
'wasm/module-decoder-unittest.cc',
|
||||
|
155
test/unittests/wasm/wasm-heap-unittest.cc
Normal file
155
test/unittests/wasm/wasm-heap-unittest.cc
Normal file
@ -0,0 +1,155 @@
|
||||
// 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-heap.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
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.Release(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.Release(std::move(b));
|
||||
CheckLooksLike(a, {{1, 5}});
|
||||
DisjointAllocationPool c;
|
||||
a.Release(std::move(c));
|
||||
CheckLooksLike(a, {{1, 5}});
|
||||
DisjointAllocationPool e, f;
|
||||
e.Release(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.Release(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}});
|
||||
CheckLooksLike(a, {});
|
||||
a.Release(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.Release(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.Release(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.Release(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.Release(Make({{15, 20}}));
|
||||
CheckLooksLike(a, {{10, 25}});
|
||||
}
|
||||
|
||||
TEST_F(DisjointAllocationPoolTest, MergingMore) {
|
||||
DisjointAllocationPool a = Make({{10, 15}, {20, 25}, {30, 35}});
|
||||
a.Release(Make({{15, 20}, {25, 30}}));
|
||||
CheckLooksLike(a, {{10, 35}});
|
||||
}
|
||||
|
||||
TEST_F(DisjointAllocationPoolTest, MergingSkip) {
|
||||
DisjointAllocationPool a = Make({{10, 15}, {20, 25}, {30, 35}});
|
||||
a.Release(Make({{25, 30}}));
|
||||
CheckLooksLike(a, {{10, 15}, {20, 35}});
|
||||
}
|
||||
|
||||
TEST_F(DisjointAllocationPoolTest, MergingSkipLargerSrc) {
|
||||
DisjointAllocationPool a = Make({{10, 15}, {20, 25}, {30, 35}});
|
||||
a.Release(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.Release(Make({{25, 30}, {36, 40}}));
|
||||
CheckLooksLike(a, {{10, 15}, {20, 35}, {36, 40}});
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
Loading…
Reference in New Issue
Block a user