1 /*
2  *    Stack-less Just-In-Time compiler
3  *
4  *    Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without modification, are
7  * permitted provided that the following conditions are met:
8  *
9  *   1. Redistributions of source code must retain the above copyright notice, this list of
10  *      conditions and the following disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above copyright notice, this list
13  *      of conditions and the following disclaimer in the documentation and/or other materials
14  *      provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
19  * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
22  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
24  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 /*
28    This file contains a simple executable memory allocator
29 
30    It is assumed, that executable code blocks are usually medium (or sometimes
31    large) memory blocks, and the allocator is not too frequently called (less
32    optimized than other allocators). Thus, using it as a generic allocator is
33    not suggested.
34 
35    How does it work:
36      Memory is allocated in continuous memory areas called chunks by alloc_chunk()
37      Chunk format:
38      [ block ][ block ] ... [ block ][ block terminator ]
39 
40    All blocks and the block terminator is started with block_header. The block
41    header contains the size of the previous and the next block. These sizes
42    can also contain special values.
43      Block size:
44        0 - The block is a free_block, with a different size member.
45        1 - The block is a block terminator.
46        n - The block is used at the moment, and the value contains its size.
47      Previous block size:
48        0 - This is the first block of the memory chunk.
49        n - The size of the previous block.
50 
51    Using these size values we can go forward or backward on the block chain.
52    The unused blocks are stored in a chain list pointed by free_blocks. This
53    list is useful if we need to find a suitable memory area when the allocator
54    is called.
55 
56    When a block is freed, the new free block is connected to its adjacent free
57    blocks if possible.
58 
59      [ free block ][ used block ][ free block ]
60    and "used block" is freed, the three blocks are connected together:
61      [           one big free block           ]
62 */
63 
64 /* --------------------------------------------------------------------- */
65 /*  System (OS) functions                                                */
66 /* --------------------------------------------------------------------- */
67 
68 /* 64 KByte. */
69 #define CHUNK_SIZE	0x10000
70 
71 /*
72    alloc_chunk / free_chunk :
73      * allocate executable system memory chunks
74      * the size is always divisible by CHUNK_SIZE
75    allocator_grab_lock / allocator_release_lock :
76      * make the allocator thread safe
77      * can be empty if the OS (or the application) does not support threading
78      * only the allocator requires this lock, sljit is fully thread safe
79        as it only uses local variables
80 */
81 
82 #ifdef _WIN32
83 
alloc_chunk(sljit_uw size)84 static SLJIT_INLINE void* alloc_chunk(sljit_uw size)
85 {
86 	return VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
87 }
88 
free_chunk(void * chunk,sljit_uw size)89 static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
90 {
91 	SLJIT_UNUSED_ARG(size);
92 	VirtualFree(chunk, 0, MEM_RELEASE);
93 }
94 
95 #else
96 
97 #ifdef __APPLE__
98 /* Configures TARGET_OS_OSX when appropriate */
99 #include <TargetConditionals.h>
100 
101 #if TARGET_OS_OSX && defined(MAP_JIT)
102 #include <sys/utsname.h>
103 #endif /* TARGET_OS_OSX && MAP_JIT */
104 
105 #ifdef MAP_JIT
106 
get_map_jit_flag()107 static SLJIT_INLINE int get_map_jit_flag()
108 {
109 #if TARGET_OS_OSX
110 	/* On macOS systems, returns MAP_JIT if it is defined _and_ we're running on a version
111 	   of macOS where it's OK to have more than one JIT block. On non-macOS systems, returns
112 	   MAP_JIT if it is defined. */
113 	static int map_jit_flag = -1;
114 
115 	/* The following code is thread safe because multiple initialization
116 	   sets map_jit_flag to the same value and the code has no side-effects.
117 	   Changing the kernel version witout system restart is (very) unlikely. */
118 	if (map_jit_flag == -1) {
119 		struct utsname name;
120 
121 		uname(&name);
122 
123 		/* Kernel version for 10.14.0 (Mojave) */
124 		if (atoi(name.release) >= 18) {
125 			/* Only use MAP_JIT if a hardened runtime is used, because MAP_JIT is incompatible
126 			   with fork(). */
127 			void *ptr = mmap(
128 				NULL, getpagesize(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
129 			if (ptr == MAP_FAILED) {
130 				map_jit_flag = MAP_JIT;
131 			} else {
132 				map_jit_flag = 0;
133 				munmap(ptr, getpagesize());
134 			}
135 		} else {
136 			map_jit_flag = 0;
137 		}
138 	}
139 
140 	return map_jit_flag;
141 #else /* !TARGET_OS_OSX */
142 	return MAP_JIT;
143 #endif /* TARGET_OS_OSX */
144 }
145 
146 #endif /* MAP_JIT */
147 
148 #endif /* __APPLE__ */
149 
alloc_chunk(sljit_uw size)150 static SLJIT_INLINE void* alloc_chunk(sljit_uw size)
151 {
152 	void *retval;
153 
154 #ifdef MAP_ANON
155 
156 	int flags = MAP_PRIVATE | MAP_ANON;
157 
158 #ifdef MAP_JIT
159 	flags |= get_map_jit_flag();
160 #endif
161 
162 	retval = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, flags, -1, 0);
163 #else /* !MAP_ANON */
164 	if (dev_zero < 0) {
165 		if (open_dev_zero())
166 			return NULL;
167 	}
168 	retval = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE, dev_zero, 0);
169 #endif /* MAP_ANON */
170 
171 	return (retval != MAP_FAILED) ? retval : NULL;
172 }
173 
free_chunk(void * chunk,sljit_uw size)174 static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
175 {
176 	munmap(chunk, size);
177 }
178 
179 #endif
180 
181 /* --------------------------------------------------------------------- */
182 /*  Common functions                                                     */
183 /* --------------------------------------------------------------------- */
184 
185 #define CHUNK_MASK	(~(CHUNK_SIZE - 1))
186 
187 struct block_header {
188 	sljit_uw size;
189 	sljit_uw prev_size;
190 };
191 
192 struct free_block {
193 	struct block_header header;
194 	struct free_block *next;
195 	struct free_block *prev;
196 	sljit_uw size;
197 };
198 
199 #define AS_BLOCK_HEADER(base, offset) \
200 	((struct block_header*)(((sljit_u8*)base) + offset))
201 #define AS_FREE_BLOCK(base, offset) \
202 	((struct free_block*)(((sljit_u8*)base) + offset))
203 #define MEM_START(base)		((void*)(((sljit_u8*)base) + sizeof(struct block_header)))
204 #define ALIGN_SIZE(size)	(((size) + sizeof(struct block_header) + 7) & ~7)
205 
206 static struct free_block* free_blocks;
207 static sljit_uw allocated_size;
208 static sljit_uw total_size;
209 
sljit_insert_free_block(struct free_block * free_block,sljit_uw size)210 static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size)
211 {
212 	free_block->header.size = 0;
213 	free_block->size = size;
214 
215 	free_block->next = free_blocks;
216 	free_block->prev = NULL;
217 	if (free_blocks)
218 		free_blocks->prev = free_block;
219 	free_blocks = free_block;
220 }
221 
sljit_remove_free_block(struct free_block * free_block)222 static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block)
223 {
224 	if (free_block->next)
225 		free_block->next->prev = free_block->prev;
226 
227 	if (free_block->prev)
228 		free_block->prev->next = free_block->next;
229 	else {
230 		SLJIT_ASSERT(free_blocks == free_block);
231 		free_blocks = free_block->next;
232 	}
233 }
234 
sljit_malloc_exec(sljit_uw size)235 SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
236 {
237 	struct block_header *header;
238 	struct block_header *next_header;
239 	struct free_block *free_block;
240 	sljit_uw chunk_size;
241 
242 	allocator_grab_lock();
243 	if (size < (64 - sizeof(struct block_header)))
244 		size = (64 - sizeof(struct block_header));
245 	size = ALIGN_SIZE(size);
246 
247 	free_block = free_blocks;
248 	while (free_block) {
249 		if (free_block->size >= size) {
250 			chunk_size = free_block->size;
251 			if (chunk_size > size + 64) {
252 				/* We just cut a block from the end of the free block. */
253 				chunk_size -= size;
254 				free_block->size = chunk_size;
255 				header = AS_BLOCK_HEADER(free_block, chunk_size);
256 				header->prev_size = chunk_size;
257 				AS_BLOCK_HEADER(header, size)->prev_size = size;
258 			}
259 			else {
260 				sljit_remove_free_block(free_block);
261 				header = (struct block_header*)free_block;
262 				size = chunk_size;
263 			}
264 			allocated_size += size;
265 			header->size = size;
266 			allocator_release_lock();
267 			return MEM_START(header);
268 		}
269 		free_block = free_block->next;
270 	}
271 
272 	chunk_size = (size + sizeof(struct block_header) + CHUNK_SIZE - 1) & CHUNK_MASK;
273 	header = (struct block_header*)alloc_chunk(chunk_size);
274 	if (!header) {
275 		allocator_release_lock();
276 		return NULL;
277 	}
278 
279 	chunk_size -= sizeof(struct block_header);
280 	total_size += chunk_size;
281 
282 	header->prev_size = 0;
283 	if (chunk_size > size + 64) {
284 		/* Cut the allocated space into a free and a used block. */
285 		allocated_size += size;
286 		header->size = size;
287 		chunk_size -= size;
288 
289 		free_block = AS_FREE_BLOCK(header, size);
290 		free_block->header.prev_size = size;
291 		sljit_insert_free_block(free_block, chunk_size);
292 		next_header = AS_BLOCK_HEADER(free_block, chunk_size);
293 	}
294 	else {
295 		/* All space belongs to this allocation. */
296 		allocated_size += chunk_size;
297 		header->size = chunk_size;
298 		next_header = AS_BLOCK_HEADER(header, chunk_size);
299 	}
300 	next_header->size = 1;
301 	next_header->prev_size = chunk_size;
302 	allocator_release_lock();
303 	return MEM_START(header);
304 }
305 
sljit_free_exec(void * ptr)306 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
307 {
308 	struct block_header *header;
309 	struct free_block* free_block;
310 
311 	allocator_grab_lock();
312 	header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header));
313 	allocated_size -= header->size;
314 
315 	/* Connecting free blocks together if possible. */
316 
317 	/* If header->prev_size == 0, free_block will equal to header.
318 	   In this case, free_block->header.size will be > 0. */
319 	free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size);
320 	if (SLJIT_UNLIKELY(!free_block->header.size)) {
321 		free_block->size += header->size;
322 		header = AS_BLOCK_HEADER(free_block, free_block->size);
323 		header->prev_size = free_block->size;
324 	}
325 	else {
326 		free_block = (struct free_block*)header;
327 		sljit_insert_free_block(free_block, header->size);
328 	}
329 
330 	header = AS_BLOCK_HEADER(free_block, free_block->size);
331 	if (SLJIT_UNLIKELY(!header->size)) {
332 		free_block->size += ((struct free_block*)header)->size;
333 		sljit_remove_free_block((struct free_block*)header);
334 		header = AS_BLOCK_HEADER(free_block, free_block->size);
335 		header->prev_size = free_block->size;
336 	}
337 
338 	/* The whole chunk is free. */
339 	if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) {
340 		/* If this block is freed, we still have (allocated_size / 2) free space. */
341 		if (total_size - free_block->size > (allocated_size * 3 / 2)) {
342 			total_size -= free_block->size;
343 			sljit_remove_free_block(free_block);
344 			free_chunk(free_block, free_block->size + sizeof(struct block_header));
345 		}
346 	}
347 
348 	allocator_release_lock();
349 }
350 
sljit_free_unused_memory_exec(void)351 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void)
352 {
353 	struct free_block* free_block;
354 	struct free_block* next_free_block;
355 
356 	allocator_grab_lock();
357 
358 	free_block = free_blocks;
359 	while (free_block) {
360 		next_free_block = free_block->next;
361 		if (!free_block->header.prev_size &&
362 				AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) {
363 			total_size -= free_block->size;
364 			sljit_remove_free_block(free_block);
365 			free_chunk(free_block, free_block->size + sizeof(struct block_header));
366 		}
367 		free_block = next_free_block;
368 	}
369 
370 	SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks));
371 	allocator_release_lock();
372 }
373