1/*
2 * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#ifndef RegisterFile_h
30#define RegisterFile_h
31
32#include "Collector.h"
33#include "ExecutableAllocator.h"
34#include "Register.h"
35#include <stdio.h>
36#include <wtf/Noncopyable.h>
37#include <wtf/VMTags.h>
38
39#if HAVE(MMAP)
40#include <errno.h>
41#include <sys/mman.h>
42#endif
43
44namespace JSC {
45
46/*
47    A register file is a stack of register frames. We represent a register
48    frame by its offset from "base", the logical first entry in the register
49    file. The bottom-most register frame's offset from base is 0.
50
51    In a program where function "a" calls function "b" (global code -> a -> b),
52    the register file might look like this:
53
54    |       global frame     |        call frame      |        call frame      |     spare capacity     |
55    -----------------------------------------------------------------------------------------------------
56    |  0 |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 | 13 | 14 |    |    |    |    |    | <-- index in buffer
57    -----------------------------------------------------------------------------------------------------
58    | -3 | -2 | -1 |  0 |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 |    |    |    |    |    | <-- index relative to base
59    -----------------------------------------------------------------------------------------------------
60    |    <-globals | temps-> |  <-vars | temps->      |                 <-vars |
61       ^              ^                   ^                                       ^
62       |              |                   |                                       |
63     buffer    base (frame 0)          frame 1                                 frame 2
64
65    Since all variables, including globals, are accessed by negative offsets
66    from their register frame pointers, to keep old global offsets correct, new
67    globals must appear at the beginning of the register file, shifting base
68    to the right.
69
70    If we added one global variable to the register file depicted above, it
71    would look like this:
72
73    |         global frame        |<                                                                    >
74    ------------------------------->                                                                    <
75    |  0 |  1 |  2 |  3 |  4 |  5 |<                             >snip<                                 > <-- index in buffer
76    ------------------------------->                                                                    <
77    | -4 | -3 | -2 | -1 |  0 |  1 |<                                                                    > <-- index relative to base
78    ------------------------------->                                                                    <
79    |         <-globals | temps-> |
80       ^                   ^
81       |                   |
82     buffer         base (frame 0)
83
84    As you can see, global offsets relative to base have stayed constant,
85    but base itself has moved. To keep up with possible changes to base,
86    clients keep an indirect pointer, so their calculations update
87    automatically when base changes.
88
89    For client simplicity, the RegisterFile measures size and capacity from
90    "base", not "buffer".
91*/
92
93    class JSGlobalObject;
94
95    class RegisterFile : public Noncopyable {
96        friend class JIT;
97    public:
98        enum CallFrameHeaderEntry {
99            CallFrameHeaderSize = 8,
100
101            CodeBlock = -8,
102            ScopeChain = -7,
103            CallerFrame = -6,
104            ReturnPC = -5, // This is either an Instruction* or a pointer into JIT generated code stored as an Instruction*.
105            ReturnValueRegister = -4,
106            ArgumentCount = -3,
107            Callee = -2,
108            OptionalCalleeArguments = -1,
109        };
110
111        enum { ProgramCodeThisRegister = -CallFrameHeaderSize - 1 };
112        enum { ArgumentsRegister = 0 };
113
114        static const size_t defaultCapacity = 524288;
115        static const size_t defaultMaxGlobals = 8192;
116        static const size_t commitSize = 1 << 14;
117        // Allow 8k of excess registers before we start trying to reap the registerfile
118        static const ptrdiff_t maxExcessCapacity = 8 * 1024;
119
120        RegisterFile(size_t capacity = defaultCapacity, size_t maxGlobals = defaultMaxGlobals);
121        ~RegisterFile();
122
123        Register* start() const { return m_start; }
124        Register* end() const { return m_end; }
125        size_t size() const { return m_end - m_start; }
126
127        void setGlobalObject(JSGlobalObject* globalObject) { m_globalObject = globalObject; }
128        JSGlobalObject* globalObject() { return m_globalObject; }
129
130        bool grow(Register* newEnd);
131        void shrink(Register* newEnd);
132
133        void setNumGlobals(size_t numGlobals) { m_numGlobals = numGlobals; }
134        int numGlobals() const { return m_numGlobals; }
135        size_t maxGlobals() const { return m_maxGlobals; }
136
137        Register* lastGlobal() const { return m_start - m_numGlobals; }
138
139        void markGlobals(MarkStack& markStack, Heap* heap) { heap->markConservatively(markStack, lastGlobal(), m_start); }
140        void markCallFrames(MarkStack& markStack, Heap* heap) { heap->markConservatively(markStack, m_start, m_end); }
141
142    private:
143        void releaseExcessCapacity();
144        size_t m_numGlobals;
145        const size_t m_maxGlobals;
146        Register* m_start;
147        Register* m_end;
148        Register* m_max;
149        Register* m_buffer;
150        Register* m_maxUsed;
151
152#if HAVE(VIRTUALALLOC)
153        Register* m_commitEnd;
154#endif
155
156        JSGlobalObject* m_globalObject; // The global object whose vars are currently stored in the register file.
157    };
158
159    // FIXME: Add a generic getpagesize() to WTF, then move this function to WTF as well.
160    inline bool isPageAligned(size_t size) { return size != 0 && size % (8 * 1024) == 0; }
161
162    inline RegisterFile::RegisterFile(size_t capacity, size_t maxGlobals)
163        : m_numGlobals(0)
164        , m_maxGlobals(maxGlobals)
165        , m_start(0)
166        , m_end(0)
167        , m_max(0)
168        , m_buffer(0)
169        , m_globalObject(0)
170    {
171        // Verify that our values will play nice with mmap and VirtualAlloc.
172        ASSERT(isPageAligned(maxGlobals));
173        ASSERT(isPageAligned(capacity));
174
175        size_t bufferLength = (capacity + maxGlobals) * sizeof(Register);
176    #if HAVE(MMAP)
177        m_buffer = static_cast<Register*>(mmap(0, bufferLength, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, VM_TAG_FOR_REGISTERFILE_MEMORY, 0));
178        if (m_buffer == MAP_FAILED) {
179#if OS(WINCE)
180            fprintf(stderr, "Could not allocate register file: %d\n", GetLastError());
181#else
182            fprintf(stderr, "Could not allocate register file: %d\n", errno);
183#endif
184            CRASH();
185        }
186    #elif HAVE(VIRTUALALLOC)
187        m_buffer = static_cast<Register*>(VirtualAlloc(0, roundUpAllocationSize(bufferLength, commitSize), MEM_RESERVE, PAGE_READWRITE));
188        if (!m_buffer) {
189#if OS(WINCE)
190            fprintf(stderr, "Could not allocate register file: %d\n", GetLastError());
191#else
192            fprintf(stderr, "Could not allocate register file: %d\n", errno);
193#endif
194            CRASH();
195        }
196        size_t committedSize = roundUpAllocationSize(maxGlobals * sizeof(Register), commitSize);
197        void* commitCheck = VirtualAlloc(m_buffer, committedSize, MEM_COMMIT, PAGE_READWRITE);
198        if (commitCheck != m_buffer) {
199#if OS(WINCE)
200            fprintf(stderr, "Could not allocate register file: %d\n", GetLastError());
201#else
202            fprintf(stderr, "Could not allocate register file: %d\n", errno);
203#endif
204            CRASH();
205        }
206        m_commitEnd = reinterpret_cast<Register*>(reinterpret_cast<char*>(m_buffer) + committedSize);
207    #else
208        /*
209         * If neither MMAP nor VIRTUALALLOC are available - use fastMalloc instead.
210         *
211         * Please note that this is the fallback case, which is non-optimal.
212         * If any possible, the platform should provide for a better memory
213         * allocation mechanism that allows for "lazy commit" or dynamic
214         * pre-allocation, similar to mmap or VirtualAlloc, to avoid waste of memory.
215         */
216        m_buffer = static_cast<Register*>(fastMalloc(bufferLength));
217    #endif
218        m_start = m_buffer + maxGlobals;
219        m_end = m_start;
220        m_maxUsed = m_end;
221        m_max = m_start + capacity;
222    }
223
224    inline void RegisterFile::shrink(Register* newEnd)
225    {
226        if (newEnd >= m_end)
227            return;
228        m_end = newEnd;
229        if (m_end == m_start && (m_maxUsed - m_start) > maxExcessCapacity)
230            releaseExcessCapacity();
231    }
232
233    inline bool RegisterFile::grow(Register* newEnd)
234    {
235        if (newEnd < m_end)
236            return true;
237
238        if (newEnd > m_max)
239            return false;
240
241#if !HAVE(MMAP) && HAVE(VIRTUALALLOC)
242        if (newEnd > m_commitEnd) {
243            size_t size = roundUpAllocationSize(reinterpret_cast<char*>(newEnd) - reinterpret_cast<char*>(m_commitEnd), commitSize);
244            if (!VirtualAlloc(m_commitEnd, size, MEM_COMMIT, PAGE_READWRITE)) {
245#if OS(WINCE)
246                fprintf(stderr, "Could not allocate register file: %d\n", GetLastError());
247#else
248                fprintf(stderr, "Could not allocate register file: %d\n", errno);
249#endif
250                CRASH();
251            }
252            m_commitEnd = reinterpret_cast<Register*>(reinterpret_cast<char*>(m_commitEnd) + size);
253        }
254#endif
255
256        if (newEnd > m_maxUsed)
257            m_maxUsed = newEnd;
258
259        m_end = newEnd;
260        return true;
261    }
262
263} // namespace JSC
264
265#endif // RegisterFile_h
266