summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testGCAllocator.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jsapi-tests/testGCAllocator.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jsapi-tests/testGCAllocator.cpp')
-rw-r--r--js/src/jsapi-tests/testGCAllocator.cpp354
1 files changed, 354 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testGCAllocator.cpp b/js/src/jsapi-tests/testGCAllocator.cpp
new file mode 100644
index 0000000000..c740a3b10b
--- /dev/null
+++ b/js/src/jsapi-tests/testGCAllocator.cpp
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+/* 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/Memory.h"
+#include "jsapi-tests/tests.h"
+
+#if defined(XP_WIN)
+# include "util/WindowsWrapper.h"
+# include <psapi.h>
+#elif defined(__wasi__)
+// Nothing.
+#else
+# include <algorithm>
+# include <errno.h>
+# include <sys/mman.h>
+# include <sys/resource.h>
+# include <sys/stat.h>
+# include <sys/types.h>
+# include <unistd.h>
+#endif
+
+BEGIN_TEST(testGCAllocator) {
+#ifdef JS_64BIT
+ // If we're using the scattershot allocator, this test does not apply.
+ if (js::gc::UsingScattershotAllocator()) {
+ return true;
+ }
+#endif
+
+ size_t PageSize = js::gc::SystemPageSize();
+
+ /* Finish any ongoing background free activity. */
+ js::gc::FinishGC(cx);
+
+ bool growUp = false;
+ CHECK(addressesGrowUp(&growUp));
+
+ if (growUp) {
+ return testGCAllocatorUp(PageSize);
+ } else {
+ 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));
+ // Check that the last ditch allocator can deal with naturally aligned chunks.
+ CHECK(positionIsCorrect("x--xx--xoo------", 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));
+ // Check that the last ditch allocator can deal with naturally aligned chunks.
+ CHECK(positionIsCorrect("------oox--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)
+
+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));
+}
+
+#elif defined(__wasi__)
+
+void* mapMemoryAt(void* desired, size_t length) { return nullptr; }
+
+void* mapMemory(size_t length) {
+ void* addr = nullptr;
+ if (int err = posix_memalign(&addr, js::gc::SystemPageSize(), length)) {
+ MOZ_ASSERT(err == ENOMEM);
+ }
+ MOZ_ASSERT(addr);
+ memset(addr, 0, length);
+ return addr;
+}
+
+void unmapPages(void* p, size_t size) { free(p); }
+
+#else
+
+void* mapMemoryAt(void* desired, size_t length) {
+ 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;
+ void* region = mmap(nullptr, length, prot, flags, fd, offset);
+ if (region == MAP_FAILED) {
+ return nullptr;
+ }
+ return region;
+}
+
+void unmapPages(void* p, size_t size) {
+ if (munmap(p, size)) {
+ MOZ_RELEASE_ASSERT(errno == ENOMEM);
+ }
+}
+
+#endif
+
+END_TEST(testGCAllocator)