/* | |
* Copyright (C) 2008, 2009 Apple Inc. All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions | |
* are met: | |
* | |
* 1. Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* 2. Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of | |
* its contributors may be used to endorse or promote products derived | |
* from this software without specific prior written permission. | |
* | |
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | |
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | |
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
#ifndef RegisterFile_h | |
#define RegisterFile_h | |
#include "Collector.h" | |
#include "ExecutableAllocator.h" | |
#include "Register.h" | |
#include <stdio.h> | |
#include <wtf/Noncopyable.h> | |
#include <wtf/VMTags.h> | |
#if HAVE(MMAP) | |
#include <errno.h> | |
#include <sys/mman.h> | |
#endif | |
#if OS(SYMBIAN) | |
#include <wtf/symbian/RegisterFileAllocatorSymbian.h> | |
#endif | |
namespace JSC { | |
/* | |
A register file is a stack of register frames. We represent a register | |
frame by its offset from "base", the logical first entry in the register | |
file. The bottom-most register frame's offset from base is 0. | |
In a program where function "a" calls function "b" (global code -> a -> b), | |
the register file might look like this: | |
| global frame | call frame | call frame | spare capacity | | |
----------------------------------------------------------------------------------------------------- | |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | | | | | | <-- index in buffer | |
----------------------------------------------------------------------------------------------------- | |
| -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | | | | | | <-- index relative to base | |
----------------------------------------------------------------------------------------------------- | |
| <-globals | temps-> | <-vars | temps-> | <-vars | | |
^ ^ ^ ^ | |
| | | | | |
buffer base (frame 0) frame 1 frame 2 | |
Since all variables, including globals, are accessed by negative offsets | |
from their register frame pointers, to keep old global offsets correct, new | |
globals must appear at the beginning of the register file, shifting base | |
to the right. | |
If we added one global variable to the register file depicted above, it | |
would look like this: | |
| global frame |< > | |
-------------------------------> < | |
| 0 | 1 | 2 | 3 | 4 | 5 |< >snip< > <-- index in buffer | |
-------------------------------> < | |
| -4 | -3 | -2 | -1 | 0 | 1 |< > <-- index relative to base | |
-------------------------------> < | |
| <-globals | temps-> | | |
^ ^ | |
| | | |
buffer base (frame 0) | |
As you can see, global offsets relative to base have stayed constant, | |
but base itself has moved. To keep up with possible changes to base, | |
clients keep an indirect pointer, so their calculations update | |
automatically when base changes. | |
For client simplicity, the RegisterFile measures size and capacity from | |
"base", not "buffer". | |
*/ | |
class JSGlobalObject; | |
class RegisterFile : public Noncopyable { | |
friend class JIT; | |
public: | |
enum CallFrameHeaderEntry { | |
CallFrameHeaderSize = 8, | |
CodeBlock = -8, | |
ScopeChain = -7, | |
CallerFrame = -6, | |
ReturnPC = -5, // This is either an Instruction* or a pointer into JIT generated code stored as an Instruction*. | |
ReturnValueRegister = -4, | |
ArgumentCount = -3, | |
Callee = -2, | |
OptionalCalleeArguments = -1 | |
}; | |
enum { ProgramCodeThisRegister = -CallFrameHeaderSize - 1 }; | |
enum { ArgumentsRegister = 0 }; | |
static const size_t defaultCapacity = 524288; | |
static const size_t defaultMaxGlobals = 8192; | |
static const size_t commitSize = 1 << 14; | |
// Allow 8k of excess registers before we start trying to reap the registerfile | |
static const ptrdiff_t maxExcessCapacity = 8 * 1024; | |
RegisterFile(size_t capacity = defaultCapacity, size_t maxGlobals = defaultMaxGlobals); | |
~RegisterFile(); | |
Register* start() const { return m_start; } | |
Register* end() const { return m_end; } | |
size_t size() const { return m_end - m_start; } | |
void setGlobalObject(JSGlobalObject* globalObject) { m_globalObject = globalObject; } | |
JSGlobalObject* globalObject() { return m_globalObject; } | |
bool grow(Register* newEnd); | |
void shrink(Register* newEnd); | |
void setNumGlobals(size_t numGlobals) { m_numGlobals = numGlobals; } | |
int numGlobals() const { return m_numGlobals; } | |
size_t maxGlobals() const { return m_maxGlobals; } | |
Register* lastGlobal() const { return m_start - m_numGlobals; } | |
void markGlobals(MarkStack& markStack, Heap* heap) { heap->markConservatively(markStack, lastGlobal(), m_start); } | |
void markCallFrames(MarkStack& markStack, Heap* heap) { heap->markConservatively(markStack, m_start, m_end); } | |
private: | |
void releaseExcessCapacity(); | |
size_t m_numGlobals; | |
const size_t m_maxGlobals; | |
Register* m_start; | |
Register* m_end; | |
Register* m_max; | |
Register* m_buffer; | |
Register* m_maxUsed; | |
#if HAVE(VIRTUALALLOC) | |
Register* m_commitEnd; | |
#endif | |
#if OS(SYMBIAN) | |
// Commits and frees a continguous chunk of memory as required | |
WTF::RegisterFileAllocator* m_registerFileAllocator; | |
#endif | |
JSGlobalObject* m_globalObject; // The global object whose vars are currently stored in the register file. | |
}; | |
// FIXME: Add a generic getpagesize() to WTF, then move this function to WTF as well. | |
// This is still a hack that should be fixed later. We know that a Symbian page size is 4K. | |
#if OS(SYMBIAN) | |
inline bool isPageAligned(size_t size) { return size && !(size % (4 * 1024)); } | |
#else | |
inline bool isPageAligned(size_t size) { return size && !(size % (8 * 1024)); } | |
#endif | |
inline RegisterFile::RegisterFile(size_t capacity, size_t maxGlobals) | |
: m_numGlobals(0) | |
, m_maxGlobals(maxGlobals) | |
, m_start(0) | |
, m_end(0) | |
, m_max(0) | |
, m_buffer(0) | |
, m_globalObject(0) | |
{ | |
// Verify that our values will play nice with mmap and VirtualAlloc. | |
ASSERT(isPageAligned(maxGlobals)); | |
ASSERT(isPageAligned(capacity)); | |
size_t bufferLength = (capacity + maxGlobals) * sizeof(Register); | |
#if HAVE(MMAP) | |
m_buffer = reinterpret_cast<Register*>(mmap(0, bufferLength, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, VM_TAG_FOR_REGISTERFILE_MEMORY, 0)); | |
if (m_buffer == MAP_FAILED) { | |
#if OS(WINCE) | |
fprintf(stderr, "Could not allocate register file: %d\n", GetLastError()); | |
#else | |
fprintf(stderr, "Could not allocate register file: %d\n", errno); | |
#endif | |
CRASH(); | |
} | |
#elif HAVE(VIRTUALALLOC) | |
m_buffer = static_cast<Register*>(VirtualAlloc(0, roundUpAllocationSize(bufferLength, commitSize), MEM_RESERVE, PAGE_READWRITE)); | |
if (!m_buffer) { | |
#if OS(WINCE) | |
fprintf(stderr, "Could not allocate register file: %d\n", GetLastError()); | |
#else | |
fprintf(stderr, "Could not allocate register file: %d\n", errno); | |
#endif | |
CRASH(); | |
} | |
size_t committedSize = roundUpAllocationSize(maxGlobals * sizeof(Register), commitSize); | |
void* commitCheck = VirtualAlloc(m_buffer, committedSize, MEM_COMMIT, PAGE_READWRITE); | |
if (commitCheck != m_buffer) { | |
#if OS(WINCE) | |
fprintf(stderr, "Could not allocate register file: %d\n", GetLastError()); | |
#else | |
fprintf(stderr, "Could not allocate register file: %d\n", errno); | |
#endif | |
CRASH(); | |
} | |
m_commitEnd = reinterpret_cast<Register*>(reinterpret_cast<char*>(m_buffer) + committedSize); | |
#elif OS(SYMBIAN) | |
m_registerFileAllocator = new WTF::RegisterFileAllocator(bufferLength); | |
m_buffer = (Register*)(m_registerFileAllocator->buffer()); | |
// start by committing enough space to hold maxGlobals | |
void* newEnd = (void*)((int)m_buffer + (maxGlobals * sizeof(Register))); | |
m_registerFileAllocator->grow(newEnd); | |
#else | |
/* | |
* If neither MMAP nor VIRTUALALLOC are available - use fastMalloc instead. | |
* | |
* Please note that this is the fallback case, which is non-optimal. | |
* If any possible, the platform should provide for a better memory | |
* allocation mechanism that allows for "lazy commit" or dynamic | |
* pre-allocation, similar to mmap or VirtualAlloc, to avoid waste of memory. | |
*/ | |
m_buffer = static_cast<Register*>(fastMalloc(bufferLength)); | |
#endif | |
m_start = m_buffer + maxGlobals; | |
m_end = m_start; | |
m_maxUsed = m_end; | |
m_max = m_start + capacity; | |
} | |
inline void RegisterFile::shrink(Register* newEnd) | |
{ | |
if (newEnd >= m_end) | |
return; | |
m_end = newEnd; | |
if (m_end == m_start && (m_maxUsed - m_start) > maxExcessCapacity) { | |
#if OS(SYMBIAN) | |
m_registerFileAllocator->shrink(newEnd); | |
#endif | |
releaseExcessCapacity(); | |
} | |
} | |
inline bool RegisterFile::grow(Register* newEnd) | |
{ | |
if (newEnd < m_end) | |
return true; | |
if (newEnd > m_max) | |
return false; | |
#if !HAVE(MMAP) && HAVE(VIRTUALALLOC) | |
if (newEnd > m_commitEnd) { | |
size_t size = roundUpAllocationSize(reinterpret_cast<char*>(newEnd) - reinterpret_cast<char*>(m_commitEnd), commitSize); | |
if (!VirtualAlloc(m_commitEnd, size, MEM_COMMIT, PAGE_READWRITE)) { | |
#if OS(WINCE) | |
fprintf(stderr, "Could not allocate register file: %d\n", GetLastError()); | |
#else | |
fprintf(stderr, "Could not allocate register file: %d\n", errno); | |
#endif | |
CRASH(); | |
} | |
m_commitEnd = reinterpret_cast<Register*>(reinterpret_cast<char*>(m_commitEnd) + size); | |
} | |
#endif | |
#if OS(SYMBIAN) | |
m_registerFileAllocator->grow((void*)newEnd); | |
#endif | |
if (newEnd > m_maxUsed) | |
m_maxUsed = newEnd; | |
m_end = newEnd; | |
return true; | |
} | |
} // namespace JSC | |
#endif // RegisterFile_h |