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    SLJIT_ALLOCATOR_LOCK / SLJIT_ALLOCATOR_UNLOCK :
76      * provided as part of sljitUtils
77      * only the allocator requires this lock, sljit is fully thread safe
78        as it only uses local variables
79 */
80 
81 #ifdef _WIN32
82 #define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec)
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 /* POSIX */
96 
97 #if defined(__APPLE__) && defined(MAP_JIT)
98 /*
99    On macOS systems, returns MAP_JIT if it is defined _and_ we're running on a
100    version where it's OK to have more than one JIT block or where MAP_JIT is
101    required.
102    On non-macOS systems, returns MAP_JIT if it is defined.
103 */
104 #include <TargetConditionals.h>
105 #if TARGET_OS_OSX
106 #if defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86
107 #ifdef MAP_ANON
108 #include <sys/utsname.h>
109 #include <stdlib.h>
110 
111 #define SLJIT_MAP_JIT	(get_map_jit_flag())
112 
get_map_jit_flag()113 static SLJIT_INLINE int get_map_jit_flag()
114 {
115 	sljit_sw page_size;
116 	void *ptr;
117 	struct utsname name;
118 	static int map_jit_flag = -1;
119 
120 	if (map_jit_flag < 0) {
121 		map_jit_flag = 0;
122 		uname(&name);
123 
124 		/* Kernel version for 10.14.0 (Mojave) or later */
125 		if (atoi(name.release) >= 18) {
126 			page_size = get_page_alignment() + 1;
127 			/* Only use MAP_JIT if a hardened runtime is used */
128 			ptr = mmap(NULL, page_size, PROT_WRITE | PROT_EXEC,
129 					MAP_PRIVATE | MAP_ANON, -1, 0);
130 
131 			if (ptr != MAP_FAILED)
132 				munmap(ptr, page_size);
133 			else
134 				map_jit_flag = MAP_JIT;
135 		}
136 	}
137 	return map_jit_flag;
138 }
139 #endif /* MAP_ANON */
140 #else /* !SLJIT_CONFIG_X86 */
141 #if !(defined SLJIT_CONFIG_ARM && SLJIT_CONFIG_ARM)
142 #error Unsupported architecture
143 #endif /* SLJIT_CONFIG_ARM */
144 #include <pthread.h>
145 
146 #define SLJIT_MAP_JIT	(MAP_JIT)
147 #define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) \
148                         apple_update_wx_flags(enable_exec)
149 
apple_update_wx_flags(sljit_s32 enable_exec)150 static SLJIT_INLINE void apple_update_wx_flags(sljit_s32 enable_exec)
151 {
152 	pthread_jit_write_protect_np(enable_exec);
153 }
154 #endif /* SLJIT_CONFIG_X86 */
155 #else /* !TARGET_OS_OSX */
156 #define SLJIT_MAP_JIT	(MAP_JIT)
157 #endif /* TARGET_OS_OSX */
158 #endif /* __APPLE__ && MAP_JIT */
159 #ifndef SLJIT_UPDATE_WX_FLAGS
160 #define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec)
161 #endif /* !SLJIT_UPDATE_WX_FLAGS */
162 #ifndef SLJIT_MAP_JIT
163 #define SLJIT_MAP_JIT	(0)
164 #endif /* !SLJIT_MAP_JIT */
165 
alloc_chunk(sljit_uw size)166 static SLJIT_INLINE void* alloc_chunk(sljit_uw size)
167 {
168 	void *retval;
169 	int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
170 	int flags = MAP_PRIVATE;
171 	int fd = -1;
172 
173 #ifdef PROT_MAX
174 	prot |= PROT_MAX(prot);
175 #endif
176 
177 #ifdef MAP_ANON
178 	flags |= MAP_ANON | SLJIT_MAP_JIT;
179 #else /* !MAP_ANON */
180 	if (SLJIT_UNLIKELY((dev_zero < 0) && open_dev_zero()))
181 		return NULL;
182 
183 	fd = dev_zero;
184 #endif /* MAP_ANON */
185 
186 	retval = mmap(NULL, size, prot, flags, fd, 0);
187 	if (retval == MAP_FAILED)
188 		return NULL;
189 
190 #ifdef __FreeBSD__
191 	/* HardenedBSD's mmap lies, so check permissions again */
192 	if (mprotect(retval, size, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) {
193 		munmap(retval, size);
194 		return NULL;
195 	}
196 #endif /* FreeBSD */
197 
198 	SLJIT_UPDATE_WX_FLAGS(retval, (uint8_t *)retval + size, 0);
199 
200 	return retval;
201 }
202 
free_chunk(void * chunk,sljit_uw size)203 static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
204 {
205 	munmap(chunk, size);
206 }
207 
208 #endif /* windows */
209 
210 /* --------------------------------------------------------------------- */
211 /*  Common functions                                                     */
212 /* --------------------------------------------------------------------- */
213 
214 #define CHUNK_MASK	(~(CHUNK_SIZE - 1))
215 
216 struct block_header {
217 	sljit_uw size;
218 	sljit_uw prev_size;
219 };
220 
221 struct free_block {
222 	struct block_header header;
223 	struct free_block *next;
224 	struct free_block *prev;
225 	sljit_uw size;
226 };
227 
228 #define AS_BLOCK_HEADER(base, offset) \
229 	((struct block_header*)(((sljit_u8*)base) + offset))
230 #define AS_FREE_BLOCK(base, offset) \
231 	((struct free_block*)(((sljit_u8*)base) + offset))
232 #define MEM_START(base)		((void*)(((sljit_u8*)base) + sizeof(struct block_header)))
233 #define ALIGN_SIZE(size)	(((size) + sizeof(struct block_header) + 7) & ~7)
234 
235 static struct free_block* free_blocks;
236 static sljit_uw allocated_size;
237 static sljit_uw total_size;
238 
sljit_insert_free_block(struct free_block * free_block,sljit_uw size)239 static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size)
240 {
241 	free_block->header.size = 0;
242 	free_block->size = size;
243 
244 	free_block->next = free_blocks;
245 	free_block->prev = NULL;
246 	if (free_blocks)
247 		free_blocks->prev = free_block;
248 	free_blocks = free_block;
249 }
250 
sljit_remove_free_block(struct free_block * free_block)251 static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block)
252 {
253 	if (free_block->next)
254 		free_block->next->prev = free_block->prev;
255 
256 	if (free_block->prev)
257 		free_block->prev->next = free_block->next;
258 	else {
259 		SLJIT_ASSERT(free_blocks == free_block);
260 		free_blocks = free_block->next;
261 	}
262 }
263 
sljit_malloc_exec(sljit_uw size)264 SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
265 {
266 	struct block_header *header;
267 	struct block_header *next_header;
268 	struct free_block *free_block;
269 	sljit_uw chunk_size;
270 
271 	SLJIT_ALLOCATOR_LOCK();
272 	if (size < (64 - sizeof(struct block_header)))
273 		size = (64 - sizeof(struct block_header));
274 	size = ALIGN_SIZE(size);
275 
276 	free_block = free_blocks;
277 	while (free_block) {
278 		if (free_block->size >= size) {
279 			chunk_size = free_block->size;
280 			SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0);
281 			if (chunk_size > size + 64) {
282 				/* We just cut a block from the end of the free block. */
283 				chunk_size -= size;
284 				free_block->size = chunk_size;
285 				header = AS_BLOCK_HEADER(free_block, chunk_size);
286 				header->prev_size = chunk_size;
287 				AS_BLOCK_HEADER(header, size)->prev_size = size;
288 			}
289 			else {
290 				sljit_remove_free_block(free_block);
291 				header = (struct block_header*)free_block;
292 				size = chunk_size;
293 			}
294 			allocated_size += size;
295 			header->size = size;
296 			SLJIT_ALLOCATOR_UNLOCK();
297 			return MEM_START(header);
298 		}
299 		free_block = free_block->next;
300 	}
301 
302 	chunk_size = (size + sizeof(struct block_header) + CHUNK_SIZE - 1) & CHUNK_MASK;
303 	header = (struct block_header*)alloc_chunk(chunk_size);
304 	if (!header) {
305 		SLJIT_ALLOCATOR_UNLOCK();
306 		return NULL;
307 	}
308 
309 	chunk_size -= sizeof(struct block_header);
310 	total_size += chunk_size;
311 
312 	header->prev_size = 0;
313 	if (chunk_size > size + 64) {
314 		/* Cut the allocated space into a free and a used block. */
315 		allocated_size += size;
316 		header->size = size;
317 		chunk_size -= size;
318 
319 		free_block = AS_FREE_BLOCK(header, size);
320 		free_block->header.prev_size = size;
321 		sljit_insert_free_block(free_block, chunk_size);
322 		next_header = AS_BLOCK_HEADER(free_block, chunk_size);
323 	}
324 	else {
325 		/* All space belongs to this allocation. */
326 		allocated_size += chunk_size;
327 		header->size = chunk_size;
328 		next_header = AS_BLOCK_HEADER(header, chunk_size);
329 	}
330 	next_header->size = 1;
331 	next_header->prev_size = chunk_size;
332 	SLJIT_ALLOCATOR_UNLOCK();
333 	return MEM_START(header);
334 }
335 
sljit_free_exec(void * ptr)336 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
337 {
338 	struct block_header *header;
339 	struct free_block* free_block;
340 
341 	SLJIT_ALLOCATOR_LOCK();
342 	header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header));
343 	allocated_size -= header->size;
344 
345 	/* Connecting free blocks together if possible. */
346 	SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0);
347 
348 	/* If header->prev_size == 0, free_block will equal to header.
349 	   In this case, free_block->header.size will be > 0. */
350 	free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size);
351 	if (SLJIT_UNLIKELY(!free_block->header.size)) {
352 		free_block->size += header->size;
353 		header = AS_BLOCK_HEADER(free_block, free_block->size);
354 		header->prev_size = free_block->size;
355 	}
356 	else {
357 		free_block = (struct free_block*)header;
358 		sljit_insert_free_block(free_block, header->size);
359 	}
360 
361 	header = AS_BLOCK_HEADER(free_block, free_block->size);
362 	if (SLJIT_UNLIKELY(!header->size)) {
363 		free_block->size += ((struct free_block*)header)->size;
364 		sljit_remove_free_block((struct free_block*)header);
365 		header = AS_BLOCK_HEADER(free_block, free_block->size);
366 		header->prev_size = free_block->size;
367 	}
368 
369 	/* The whole chunk is free. */
370 	if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) {
371 		/* If this block is freed, we still have (allocated_size / 2) free space. */
372 		if (total_size - free_block->size > (allocated_size * 3 / 2)) {
373 			total_size -= free_block->size;
374 			sljit_remove_free_block(free_block);
375 			free_chunk(free_block, free_block->size + sizeof(struct block_header));
376 		}
377 	}
378 
379 	SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 1);
380 	SLJIT_ALLOCATOR_UNLOCK();
381 }
382 
sljit_free_unused_memory_exec(void)383 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void)
384 {
385 	struct free_block* free_block;
386 	struct free_block* next_free_block;
387 
388 	SLJIT_ALLOCATOR_LOCK();
389 	SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0);
390 
391 	free_block = free_blocks;
392 	while (free_block) {
393 		next_free_block = free_block->next;
394 		if (!free_block->header.prev_size &&
395 				AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) {
396 			total_size -= free_block->size;
397 			sljit_remove_free_block(free_block);
398 			free_chunk(free_block, free_block->size + sizeof(struct block_header));
399 		}
400 		free_block = next_free_block;
401 	}
402 
403 	SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks));
404 	SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 1);
405 	SLJIT_ALLOCATOR_UNLOCK();
406 }
407