diff options
Diffstat (limited to 'js/src/jsapi-tests/testGCAllocator.cpp')
-rw-r--r-- | js/src/jsapi-tests/testGCAllocator.cpp | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testGCAllocator.cpp b/js/src/jsapi-tests/testGCAllocator.cpp new file mode 100644 index 000000000..2c5c58a29 --- /dev/null +++ b/js/src/jsapi-tests/testGCAllocator.cpp @@ -0,0 +1,383 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- +* vim: set ts=8 sts=4 et sw=4 tw=99: +*/ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <cstdlib> + +#include "gc/GCInternals.h" +#include "gc/Memory.h" +#include "jsapi-tests/tests.h" + +#if defined(XP_WIN) +#include "jswin.h" +#include <psapi.h> +#elif defined(SOLARIS) +// This test doesn't apply to Solaris. +#elif defined(XP_UNIX) +#include <algorithm> +#include <errno.h> +#include <sys/mman.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#else +#error "Memory mapping functions are not defined for your OS." +#endif + +BEGIN_TEST(testGCAllocator) +{ + size_t PageSize = 0; +#if defined(XP_WIN) +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + PageSize = sysinfo.dwPageSize; +# else // Various APIs are unavailable. This test is disabled. + return true; +# endif +#elif defined(SOLARIS) + return true; +#elif defined(XP_UNIX) + PageSize = size_t(sysconf(_SC_PAGESIZE)); +#else + return true; +#endif + + /* Finish any ongoing background free activity. */ + js::gc::FinishGC(cx); + + bool growUp; + CHECK(addressesGrowUp(&growUp)); + + if (growUp) + return testGCAllocatorUp(PageSize); + return testGCAllocatorDown(PageSize); +} + +static const size_t Chunk = 512 * 1024; +static const size_t Alignment = 2 * Chunk; +static const int MaxTempChunks = 4096; +static const size_t StagingSize = 16 * Chunk; + +bool +addressesGrowUp(bool* resultOut) +{ + /* + * Try to detect whether the OS allocates memory in increasing or decreasing + * address order by making several allocations and comparing the addresses. + */ + + static const unsigned ChunksToTest = 20; + static const int ThresholdCount = 15; + + void* chunks[ChunksToTest]; + for (unsigned i = 0; i < ChunksToTest; i++) { + chunks[i] = mapMemory(2 * Chunk); + CHECK(chunks[i]); + } + + int upCount = 0; + int downCount = 0; + + for (unsigned i = 0; i < ChunksToTest - 1; i++) { + if (chunks[i] < chunks[i + 1]) + upCount++; + else + downCount++; + } + + for (unsigned i = 0; i < ChunksToTest; i++) + unmapPages(chunks[i], 2 * Chunk); + + /* Check results were mostly consistent. */ + CHECK(abs(upCount - downCount) >= ThresholdCount); + + *resultOut = upCount > downCount; + + return true; +} + +size_t +offsetFromAligned(void* p) +{ + return uintptr_t(p) % Alignment; +} + +enum AllocType { + UseNormalAllocator, + UseLastDitchAllocator +}; + +bool +testGCAllocatorUp(const size_t PageSize) +{ + const size_t UnalignedSize = StagingSize + Alignment - PageSize; + void* chunkPool[MaxTempChunks]; + // Allocate a contiguous chunk that we can partition for testing. + void* stagingArea = mapMemory(UnalignedSize); + if (!stagingArea) + return false; + // Ensure that the staging area is aligned. + unmapPages(stagingArea, UnalignedSize); + if (offsetFromAligned(stagingArea)) { + const size_t Offset = offsetFromAligned(stagingArea); + // Place the area at the lowest aligned address. + stagingArea = (void*)(uintptr_t(stagingArea) + (Alignment - Offset)); + } + mapMemoryAt(stagingArea, StagingSize); + // Make sure there are no available chunks below the staging area. + int tempChunks; + if (!fillSpaceBeforeStagingArea(tempChunks, stagingArea, chunkPool, false)) + return false; + // Unmap the staging area so we can set it up for testing. + unmapPages(stagingArea, StagingSize); + // Check that the first chunk is used if it is aligned. + CHECK(positionIsCorrect("xxooxxx---------", stagingArea, chunkPool, tempChunks)); + // Check that the first chunk is used if it can be aligned. + CHECK(positionIsCorrect("x-ooxxx---------", stagingArea, chunkPool, tempChunks)); + // Check that an aligned chunk after a single unalignable chunk is used. + CHECK(positionIsCorrect("x--xooxxx-------", stagingArea, chunkPool, tempChunks)); + // Check that we fall back to the slow path after two unalignable chunks. + CHECK(positionIsCorrect("x--xx--xoo--xxx-", stagingArea, chunkPool, tempChunks)); + // Check that we also fall back after an unalignable and an alignable chunk. + CHECK(positionIsCorrect("x--xx---x-oo--x-", stagingArea, chunkPool, tempChunks)); + // Check that the last ditch allocator works as expected. + CHECK(positionIsCorrect("x--xx--xx-oox---", stagingArea, chunkPool, tempChunks, + UseLastDitchAllocator)); + + // Clean up. + while (--tempChunks >= 0) + unmapPages(chunkPool[tempChunks], 2 * Chunk); + return true; +} + +bool +testGCAllocatorDown(const size_t PageSize) +{ + const size_t UnalignedSize = StagingSize + Alignment - PageSize; + void* chunkPool[MaxTempChunks]; + // Allocate a contiguous chunk that we can partition for testing. + void* stagingArea = mapMemory(UnalignedSize); + if (!stagingArea) + return false; + // Ensure that the staging area is aligned. + unmapPages(stagingArea, UnalignedSize); + if (offsetFromAligned(stagingArea)) { + void* stagingEnd = (void*)(uintptr_t(stagingArea) + UnalignedSize); + const size_t Offset = offsetFromAligned(stagingEnd); + // Place the area at the highest aligned address. + stagingArea = (void*)(uintptr_t(stagingEnd) - Offset - StagingSize); + } + mapMemoryAt(stagingArea, StagingSize); + // Make sure there are no available chunks above the staging area. + int tempChunks; + if (!fillSpaceBeforeStagingArea(tempChunks, stagingArea, chunkPool, true)) + return false; + // Unmap the staging area so we can set it up for testing. + unmapPages(stagingArea, StagingSize); + // Check that the first chunk is used if it is aligned. + CHECK(positionIsCorrect("---------xxxooxx", stagingArea, chunkPool, tempChunks)); + // Check that the first chunk is used if it can be aligned. + CHECK(positionIsCorrect("---------xxxoo-x", stagingArea, chunkPool, tempChunks)); + // Check that an aligned chunk after a single unalignable chunk is used. + CHECK(positionIsCorrect("-------xxxoox--x", stagingArea, chunkPool, tempChunks)); + // Check that we fall back to the slow path after two unalignable chunks. + CHECK(positionIsCorrect("-xxx--oox--xx--x", stagingArea, chunkPool, tempChunks)); + // Check that we also fall back after an unalignable and an alignable chunk. + CHECK(positionIsCorrect("-x--oo-x---xx--x", stagingArea, chunkPool, tempChunks)); + // Check that the last ditch allocator works as expected. + CHECK(positionIsCorrect("---xoo-xx--xx--x", stagingArea, chunkPool, tempChunks, + UseLastDitchAllocator)); + + // Clean up. + while (--tempChunks >= 0) + unmapPages(chunkPool[tempChunks], 2 * Chunk); + return true; +} + +bool +fillSpaceBeforeStagingArea(int& tempChunks, void* stagingArea, + void** chunkPool, bool addressesGrowDown) +{ + // Make sure there are no available chunks before the staging area. + tempChunks = 0; + chunkPool[tempChunks++] = mapMemory(2 * Chunk); + while (tempChunks < MaxTempChunks && chunkPool[tempChunks - 1] && + (chunkPool[tempChunks - 1] < stagingArea) ^ addressesGrowDown) { + chunkPool[tempChunks++] = mapMemory(2 * Chunk); + if (!chunkPool[tempChunks - 1]) + break; // We already have our staging area, so OOM here is okay. + if ((chunkPool[tempChunks - 1] < chunkPool[tempChunks - 2]) ^ addressesGrowDown) + break; // The address growth direction is inconsistent! + } + // OOM also means success in this case. + if (!chunkPool[tempChunks - 1]) { + --tempChunks; + return true; + } + // Bail if we can't guarantee the right address space layout. + if ((chunkPool[tempChunks - 1] < stagingArea) ^ addressesGrowDown || (tempChunks > 1 && + (chunkPool[tempChunks - 1] < chunkPool[tempChunks - 2]) ^ addressesGrowDown)) + { + while (--tempChunks >= 0) + unmapPages(chunkPool[tempChunks], 2 * Chunk); + unmapPages(stagingArea, StagingSize); + return false; + } + return true; +} + +bool +positionIsCorrect(const char* str, void* base, void** chunkPool, int tempChunks, + AllocType allocator = UseNormalAllocator) +{ + // str represents a region of memory, with each character representing a + // region of Chunk bytes. str should contain only x, o and -, where + // x = mapped by the test to set up the initial conditions, + // o = mapped by the GC allocator, and + // - = unmapped. + // base should point to a region of contiguous free memory + // large enough to hold strlen(str) chunks of Chunk bytes. + int len = strlen(str); + int i; + // Find the index of the desired address. + for (i = 0; i < len && str[i] != 'o'; ++i); + void* desired = (void*)(uintptr_t(base) + i * Chunk); + // Map the regions indicated by str. + for (i = 0; i < len; ++i) { + if (str[i] == 'x') + mapMemoryAt((void*)(uintptr_t(base) + i * Chunk), Chunk); + } + // Allocate using the GC's allocator. + void* result; + if (allocator == UseNormalAllocator) + result = js::gc::MapAlignedPages(2 * Chunk, Alignment); + else + result = js::gc::TestMapAlignedPagesLastDitch(2 * Chunk, Alignment); + // Clean up the mapped regions. + if (result) + js::gc::UnmapPages(result, 2 * Chunk); + for (--i; i >= 0; --i) { + if (str[i] == 'x') + js::gc::UnmapPages((void*)(uintptr_t(base) + i * Chunk), Chunk); + } + // CHECK returns, so clean up on failure. + if (result != desired) { + while (--tempChunks >= 0) + js::gc::UnmapPages(chunkPool[tempChunks], 2 * Chunk); + } + return result == desired; +} + +#if defined(XP_WIN) +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + +void* +mapMemoryAt(void* desired, size_t length) +{ + return VirtualAlloc(desired, length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +} + +void* +mapMemory(size_t length) +{ + return VirtualAlloc(nullptr, length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +} + +void +unmapPages(void* p, size_t size) +{ + MOZ_ALWAYS_TRUE(VirtualFree(p, 0, MEM_RELEASE)); +} + +# else // Various APIs are unavailable. This test is disabled. + +void* mapMemoryAt(void* desired, size_t length) { return nullptr; } +void* mapMemory(size_t length) { return nullptr; } +void unmapPages(void* p, size_t size) { } + +# endif +#elif defined(SOLARIS) // This test doesn't apply to Solaris. + +void* mapMemoryAt(void* desired, size_t length) { return nullptr; } +void* mapMemory(size_t length) { return nullptr; } +void unmapPages(void* p, size_t size) { } + +#elif defined(XP_UNIX) + +void* +mapMemoryAt(void* desired, size_t length) +{ +#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) || defined(__aarch64__) + MOZ_RELEASE_ASSERT(0xffff800000000000ULL & (uintptr_t(desired) + length - 1) == 0); +#endif + void* region = mmap(desired, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (region == MAP_FAILED) + return nullptr; + if (region != desired) { + if (munmap(region, length)) + MOZ_RELEASE_ASSERT(errno == ENOMEM); + return nullptr; + } + return region; +} + +void* +mapMemory(size_t length) +{ + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANON; + int fd = -1; + off_t offset = 0; + // The test code must be aligned with the implementation in gc/Memory.cpp. +#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) + void* region = mmap((void*)0x0000070000000000, length, prot, flags, fd, offset); + if (region == MAP_FAILED) + return nullptr; + if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) { + if (munmap(region, length)) + MOZ_RELEASE_ASSERT(errno == ENOMEM); + return nullptr; + } + return region; +#elif defined(__aarch64__) + const uintptr_t start = UINT64_C(0x0000070000000000); + const uintptr_t end = UINT64_C(0x0000800000000000); + const uintptr_t step = js::gc::ChunkSize; + uintptr_t hint; + void* region = MAP_FAILED; + for (hint = start; region == MAP_FAILED && hint + length <= end; hint += step) { + region = mmap((void*)hint, length, prot, flags, fd, offset); + if (region != MAP_FAILED) { + if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) { + if (munmap(region, length)) { + MOZ_RELEASE_ASSERT(errno == ENOMEM); + } + region = MAP_FAILED; + } + } + } + return region == MAP_FAILED ? nullptr : region; +#else + void* region = mmap(nullptr, length, prot, flags, fd, offset); + if (region == MAP_FAILED) + return nullptr; + return region; +#endif +} + +void +unmapPages(void* p, size_t size) +{ + if (munmap(p, size)) + MOZ_RELEASE_ASSERT(errno == ENOMEM); +} + +#else // !defined(XP_WIN) && !defined(SOLARIS) && !defined(XP_UNIX) +#error "Memory mapping functions are not defined for your OS." +#endif +END_TEST(testGCAllocator) |