Commit 3d64fa8e authored by Jakob Kummerow's avatar Jakob Kummerow Committed by Commit Bot

[tests] Introduce Multi-Mapped Mock Allocator

This new testing allocator for ArrayBuffers uses a small real allocation
that is repeatedly mapped into the requested allocation size. Its purpose
is to allow testing of huge TypedArrays without actually consuming a huge
amount of memory, at the expense of correct behavior (elements will alias
each other). It is only supported on Linux for now, and of course off by
default.

Bug: v8:4153
Change-Id: I4917a78b6190dc075dc4614ebe2696e63addc8c2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1962270
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65444}
parent fde8d2ce
......@@ -57,6 +57,10 @@
#include "unicode/locid.h"
#endif // V8_INTL_SUPPORT
#ifdef V8_OS_LINUX
#include <sys/mman.h> // For MultiMappedAllocator.
#endif
#if !defined(_WIN32) && !defined(_WIN64)
#include <unistd.h> // NOLINT
#else
......@@ -202,6 +206,66 @@ class MockArrayBufferAllocatiorWithLimit : public MockArrayBufferAllocator {
std::atomic<size_t> space_left_;
};
#ifdef V8_OS_LINUX
// This is a mock allocator variant that provides a huge virtual allocation
// backed by a small real allocation that is repeatedly mapped. If you create an
// array on memory allocated by this allocator, you will observe that elements
// will alias each other as if their indices were modulo-divided by the real
// allocation length.
// The purpose is to allow stability-testing of huge (typed) arrays without
// actually consuming huge amounts of physical memory.
// This is currently only available on Linux because it relies on {mremap}.
class MultiMappedAllocator : public ArrayBufferAllocatorBase {
protected:
void* Allocate(size_t length) override {
// We use mmap, which initializes pages to zero anyway.
return AllocateUninitialized(length);
}
void* AllocateUninitialized(size_t length) override {
size_t rounded_length = RoundUp(length, kChunkSize);
int prot = PROT_READ | PROT_WRITE;
// We have to specify MAP_SHARED to make {mremap} below do what we want.
// We specify MAP_HUGETLB to reduce TLB load (fewer, bigger mappings).
int flags = MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB;
void* real_alloc = mmap(nullptr, kChunkSize, prot, flags, -1, 0);
void* virtual_alloc =
mmap(nullptr, rounded_length, prot, flags | MAP_NORESERVE, -1, 0);
i::Address virtual_base = reinterpret_cast<i::Address>(virtual_alloc);
i::Address virtual_end = virtual_base + rounded_length;
for (i::Address to_map = virtual_base; to_map < virtual_end;
to_map += kChunkSize) {
// Specifying 0 as the "old size" causes the existing map entry to not
// get deleted, which is important so that we can remap it again in the
// next iteration of this loop.
void* result =
mremap(real_alloc, 0, kChunkSize, MREMAP_MAYMOVE | MREMAP_FIXED,
reinterpret_cast<void*>(to_map));
DCHECK_NE(-1, reinterpret_cast<intptr_t>(result));
USE(result);
}
regions_[virtual_alloc] = real_alloc;
return virtual_alloc;
}
void Free(void* data, size_t length) override {
void* real_alloc = regions_[data];
munmap(real_alloc, kChunkSize);
size_t rounded_length = RoundUp(length, kChunkSize);
munmap(data, rounded_length);
regions_.erase(data);
}
private:
// Aiming for a "Huge Page" (2M on Linux x64) to go easy on the TLB.
static constexpr size_t kChunkSize = 2 * 1024 * 1024;
std::unordered_map<void*, void*> regions_;
};
#endif // V8_OS_LINUX
v8::Platform* g_default_platform;
std::unique_ptr<v8::Platform> g_platform;
......@@ -3045,6 +3109,9 @@ bool Shell::SetOptions(int argc, char* argv[]) {
options.mock_arraybuffer_allocator = i::FLAG_mock_arraybuffer_allocator;
options.mock_arraybuffer_allocator_limit =
i::FLAG_mock_arraybuffer_allocator_limit;
#if V8_OS_LINUX
options.multi_mapped_mock_allocator = i::FLAG_multi_mapped_mock_allocator;
#endif
// Set up isolated source groups.
options.isolate_sources = new SourceGroup[options.num_isolates];
......@@ -3618,12 +3685,19 @@ int Shell::Main(int argc, char* argv[]) {
memory_limit >= options.mock_arraybuffer_allocator_limit
? memory_limit
: std::numeric_limits<size_t>::max());
#if V8_OS_LINUX
MultiMappedAllocator multi_mapped_mock_allocator;
#endif // V8_OS_LINUX
if (options.mock_arraybuffer_allocator) {
if (memory_limit) {
Shell::array_buffer_allocator = &mock_arraybuffer_allocator_with_limit;
} else {
Shell::array_buffer_allocator = &mock_arraybuffer_allocator;
}
#if V8_OS_LINUX
} else if (options.multi_mapped_mock_allocator) {
Shell::array_buffer_allocator = &multi_mapped_mock_allocator;
#endif // V8_OS_LINUX
} else {
Shell::array_buffer_allocator = &shell_array_buffer_allocator;
}
......
......@@ -274,6 +274,7 @@ class ShellOptions {
bool expected_to_throw = false;
bool mock_arraybuffer_allocator = false;
size_t mock_arraybuffer_allocator_limit = 0;
bool multi_mapped_mock_allocator = false;
bool enable_inspector = false;
int num_isolates = 1;
v8::ScriptCompiler::CompileOptions compile_options =
......
......@@ -1354,6 +1354,10 @@ DEFINE_BOOL(mock_arraybuffer_allocator, false,
DEFINE_SIZE_T(mock_arraybuffer_allocator_limit, 0,
"Memory limit for mock ArrayBuffer allocator used to simulate "
"OOM for testing.")
#if V8_OS_LINUX
DEFINE_BOOL(multi_mapped_mock_allocator, false,
"Use a multi-mapped mock ArrayBuffer allocator for testing.")
#endif
//
// GDB JIT integration flags.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment