summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testGCAllocator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jsapi-tests/testGCAllocator.cpp')
-rw-r--r--js/src/jsapi-tests/testGCAllocator.cpp383
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)