1 /*
2  * Copyright (C) 2009 University of Szeged
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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  *
14  * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL UNIVERSITY OF SZEGED OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #ifndef AssemblerBufferWithConstantPool_h
28 #define AssemblerBufferWithConstantPool_h
29 
30 #if ENABLE(ASSEMBLER)
31 
32 #include "AssemblerBuffer.h"
33 #include <wtf/SegmentedVector.h>
34 
35 #define ASSEMBLER_HAS_CONSTANT_POOL 1
36 
37 namespace JSC {
38 
39 /*
40     On a constant pool 4 or 8 bytes data can be stored. The values can be
41     constants or addresses. The addresses should be 32 or 64 bits. The constants
42     should be double-precisions float or integer numbers which are hard to be
43     encoded as few machine instructions.
44 
45     TODO: The pool is desinged to handle both 32 and 64 bits values, but
46     currently only the 4 bytes constants are implemented and tested.
47 
48     The AssemblerBuffer can contain multiple constant pools. Each pool is inserted
49     into the instruction stream - protected by a jump instruction from the
50     execution flow.
51 
52     The flush mechanism is called when no space remain to insert the next instruction
53     into the pool. Three values are used to determine when the constant pool itself
54     have to be inserted into the instruction stream (Assembler Buffer):
55 
56     - maxPoolSize: size of the constant pool in bytes, this value cannot be
57         larger than the maximum offset of a PC relative memory load
58 
59     - barrierSize: size of jump instruction in bytes which protects the
60         constant pool from execution
61 
62     - maxInstructionSize: maximum length of a machine instruction in bytes
63 
64     There are some callbacks which solve the target architecture specific
65     address handling:
66 
67     - TYPE patchConstantPoolLoad(TYPE load, int value):
68         patch the 'load' instruction with the index of the constant in the
69         constant pool and return the patched instruction.
70 
71     - void patchConstantPoolLoad(void* loadAddr, void* constPoolAddr):
72         patch the a PC relative load instruction at 'loadAddr' address with the
73         final relative offset. The offset can be computed with help of
74         'constPoolAddr' (the address of the constant pool) and index of the
75         constant (which is stored previously in the load instruction itself).
76 
77     - TYPE placeConstantPoolBarrier(int size):
78         return with a constant pool barrier instruction which jumps over the
79         constant pool.
80 
81     The 'put*WithConstant*' functions should be used to place a data into the
82     constant pool.
83 */
84 
85 template <int maxPoolSize, int barrierSize, int maxInstructionSize, class AssemblerType>
86 class AssemblerBufferWithConstantPool : public AssemblerBuffer {
87     typedef SegmentedVector<uint32_t, 512> LoadOffsets;
88     using AssemblerBuffer::putIntegral;
89     using AssemblerBuffer::putIntegralUnchecked;
90 public:
91     typedef struct {
92         short high;
93         short low;
94     } TwoShorts;
95 
96     enum {
97         UniqueConst,
98         ReusableConst,
99         UnusedEntry,
100     };
101 
AssemblerBufferWithConstantPool()102     AssemblerBufferWithConstantPool()
103         : AssemblerBuffer()
104         , m_numConsts(0)
105         , m_maxDistance(maxPoolSize)
106         , m_lastConstDelta(0)
107     {
108         m_pool = static_cast<uint32_t*>(malloc(maxPoolSize));
109         m_mask = static_cast<char*>(malloc(maxPoolSize / sizeof(uint32_t)));
110     }
111 
~AssemblerBufferWithConstantPool()112     ~AssemblerBufferWithConstantPool()
113     {
114         free(m_mask);
115         free(m_pool);
116     }
117 
ensureSpace(int space)118     void ensureSpace(int space)
119     {
120         flushIfNoSpaceFor(space);
121         AssemblerBuffer::ensureSpace(space);
122     }
123 
ensureSpace(int insnSpace,int constSpace)124     void ensureSpace(int insnSpace, int constSpace)
125     {
126         flushIfNoSpaceFor(insnSpace, constSpace);
127         AssemblerBuffer::ensureSpace(insnSpace);
128     }
129 
130     void ensureSpaceForAnyInstruction(int amount = 1)
131     {
132         flushIfNoSpaceFor(amount * maxInstructionSize, amount * sizeof(uint64_t));
133     }
134 
isAligned(int alignment)135     bool isAligned(int alignment)
136     {
137         flushIfNoSpaceFor(alignment);
138         return AssemblerBuffer::isAligned(alignment);
139     }
140 
putByteUnchecked(int value)141     void putByteUnchecked(int value)
142     {
143         AssemblerBuffer::putByteUnchecked(value);
144         correctDeltas(1);
145     }
146 
putByte(int value)147     void putByte(int value)
148     {
149         flushIfNoSpaceFor(1);
150         AssemblerBuffer::putByte(value);
151         correctDeltas(1);
152     }
153 
putShortUnchecked(int value)154     void putShortUnchecked(int value)
155     {
156         AssemblerBuffer::putShortUnchecked(value);
157         correctDeltas(2);
158     }
159 
putShort(int value)160     void putShort(int value)
161     {
162         flushIfNoSpaceFor(2);
163         AssemblerBuffer::putShort(value);
164         correctDeltas(2);
165     }
166 
putIntUnchecked(int value)167     void putIntUnchecked(int value)
168     {
169         AssemblerBuffer::putIntUnchecked(value);
170         correctDeltas(4);
171     }
172 
putInt(int value)173     void putInt(int value)
174     {
175         flushIfNoSpaceFor(4);
176         AssemblerBuffer::putInt(value);
177         correctDeltas(4);
178     }
179 
putInt64Unchecked(int64_t value)180     void putInt64Unchecked(int64_t value)
181     {
182         AssemblerBuffer::putInt64Unchecked(value);
183         correctDeltas(8);
184     }
185 
putIntegral(TwoShorts value)186     void putIntegral(TwoShorts value)
187     {
188         putIntegral(value.high);
189         putIntegral(value.low);
190     }
191 
putIntegralUnchecked(TwoShorts value)192     void putIntegralUnchecked(TwoShorts value)
193     {
194         putIntegralUnchecked(value.high);
195         putIntegralUnchecked(value.low);
196     }
197 
executableCopy(JSGlobalData & globalData,void * ownerUID,JITCompilationEffort effort)198     PassRefPtr<ExecutableMemoryHandle> executableCopy(JSGlobalData& globalData, void* ownerUID, JITCompilationEffort effort)
199     {
200         flushConstantPool(false);
201         return AssemblerBuffer::executableCopy(globalData, ownerUID, effort);
202     }
203 
204     void putShortWithConstantInt(uint16_t insn, uint32_t constant, bool isReusable = false)
205     {
206         putIntegralWithConstantInt(insn, constant, isReusable);
207     }
208 
209     void putIntWithConstantInt(uint32_t insn, uint32_t constant, bool isReusable = false)
210     {
211         putIntegralWithConstantInt(insn, constant, isReusable);
212     }
213 
214     // This flushing mechanism can be called after any unconditional jumps.
215     void flushWithoutBarrier(bool isForced = false)
216     {
217         // Flush if constant pool is more than 60% full to avoid overuse of this function.
218         if (isForced || 5 * static_cast<uint32_t>(m_numConsts) > 3 * maxPoolSize / sizeof(uint32_t))
219             flushConstantPool(false);
220     }
221 
poolAddress()222     uint32_t* poolAddress()
223     {
224         return m_pool;
225     }
226 
sizeOfConstantPool()227     int sizeOfConstantPool()
228     {
229         return m_numConsts;
230     }
231 
232 private:
correctDeltas(int insnSize)233     void correctDeltas(int insnSize)
234     {
235         m_maxDistance -= insnSize;
236         m_lastConstDelta -= insnSize;
237         if (m_lastConstDelta < 0)
238             m_lastConstDelta = 0;
239     }
240 
correctDeltas(int insnSize,int constSize)241     void correctDeltas(int insnSize, int constSize)
242     {
243         correctDeltas(insnSize);
244 
245         m_maxDistance -= m_lastConstDelta;
246         m_lastConstDelta = constSize;
247     }
248 
249     template<typename IntegralType>
putIntegralWithConstantInt(IntegralType insn,uint32_t constant,bool isReusable)250     void putIntegralWithConstantInt(IntegralType insn, uint32_t constant, bool isReusable)
251     {
252         if (!m_numConsts)
253             m_maxDistance = maxPoolSize;
254         flushIfNoSpaceFor(sizeof(IntegralType), 4);
255 
256         m_loadOffsets.append(codeSize());
257         if (isReusable) {
258             for (int i = 0; i < m_numConsts; ++i) {
259                 if (m_mask[i] == ReusableConst && m_pool[i] == constant) {
260                     putIntegral(static_cast<IntegralType>(AssemblerType::patchConstantPoolLoad(insn, i)));
261                     correctDeltas(sizeof(IntegralType));
262                     return;
263                 }
264             }
265         }
266 
267         m_pool[m_numConsts] = constant;
268         m_mask[m_numConsts] = static_cast<char>(isReusable ? ReusableConst : UniqueConst);
269 
270         putIntegral(static_cast<IntegralType>(AssemblerType::patchConstantPoolLoad(insn, m_numConsts)));
271         ++m_numConsts;
272 
273         correctDeltas(sizeof(IntegralType), 4);
274     }
275 
276     void flushConstantPool(bool useBarrier = true)
277     {
278         if (m_numConsts == 0)
279             return;
280         int alignPool = (codeSize() + (useBarrier ? barrierSize : 0)) & (sizeof(uint64_t) - 1);
281 
282         if (alignPool)
283             alignPool = sizeof(uint64_t) - alignPool;
284 
285         // Callback to protect the constant pool from execution
286         if (useBarrier)
287             putIntegral(AssemblerType::placeConstantPoolBarrier(m_numConsts * sizeof(uint32_t) + alignPool));
288 
289         if (alignPool) {
290             if (alignPool & 1)
291                 AssemblerBuffer::putByte(AssemblerType::padForAlign8);
292             if (alignPool & 2)
293                 AssemblerBuffer::putShort(AssemblerType::padForAlign16);
294             if (alignPool & 4)
295                 AssemblerBuffer::putInt(AssemblerType::padForAlign32);
296         }
297 
298         int constPoolOffset = codeSize();
299         append(reinterpret_cast<char*>(m_pool), m_numConsts * sizeof(uint32_t));
300 
301         // Patch each PC relative load
302         for (LoadOffsets::Iterator iter = m_loadOffsets.begin(); iter != m_loadOffsets.end(); ++iter) {
303             void* loadAddr = reinterpret_cast<char*>(data()) + *iter;
304             AssemblerType::patchConstantPoolLoad(loadAddr, reinterpret_cast<char*>(data()) + constPoolOffset);
305         }
306 
307         m_loadOffsets.clear();
308         m_numConsts = 0;
309     }
310 
flushIfNoSpaceFor(int nextInsnSize)311     void flushIfNoSpaceFor(int nextInsnSize)
312     {
313         if (m_numConsts == 0)
314             return;
315         int lastConstDelta = m_lastConstDelta > nextInsnSize ? m_lastConstDelta - nextInsnSize : 0;
316         if ((m_maxDistance < nextInsnSize + lastConstDelta + barrierSize + (int)sizeof(uint32_t)))
317             flushConstantPool();
318     }
319 
flushIfNoSpaceFor(int nextInsnSize,int nextConstSize)320     void flushIfNoSpaceFor(int nextInsnSize, int nextConstSize)
321     {
322         if (m_numConsts == 0)
323             return;
324         if ((m_maxDistance < nextInsnSize + m_lastConstDelta + nextConstSize + barrierSize + (int)sizeof(uint32_t)) ||
325             (m_numConsts * sizeof(uint32_t) + nextConstSize >= maxPoolSize))
326             flushConstantPool();
327     }
328 
329     uint32_t* m_pool;
330     char* m_mask;
331     LoadOffsets m_loadOffsets;
332 
333     int m_numConsts;
334     int m_maxDistance;
335     int m_lastConstDelta;
336 };
337 
338 } // namespace JSC
339 
340 #endif // ENABLE(ASSEMBLER)
341 
342 #endif // AssemblerBufferWithConstantPool_h
343