blob: f9c8058ec20a21173d2bc9032f2110767cb45250 [file] [log] [blame]
/* -*- 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::AutoFinishGC finishGC(rt);
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__))
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)
{
void* hint = nullptr;
#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__))
hint = (void*)0x0000070000000000ULL;
#endif
void* region = mmap(hint, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (region == MAP_FAILED)
return nullptr;
#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__))
if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000ULL) {
if (munmap(region, length))
MOZ_RELEASE_ASSERT(errno == ENOMEM);
return nullptr;
}
#endif
return region;
}
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)