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 /* On macOS systems, returns MAP_JIT if it is defined _and_ we're running on a version
110    of macOS where it's OK to have more than one JIT block.
111    On non-macOS systems, returns MAP_JIT if it is defined. */
112 #if TARGET_OS_OSX
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 		map_jit_flag = 0;
122 		uname(&name);
123 
124 		/* Kernel version for 10.14.0 (Mojave) */
125 		if (atoi(name.release) >= 18) {
126 			/* Only use MAP_JIT if a hardened runtime is used, because MAP_JIT is incompatible with fork(). */
127 
128 			/* mirroring page size detection from sljit_allocate_stack */
129 			long page_size = sysconf(_SC_PAGESIZE);
130 			/* Should never happen */
131 			if (page_size < 0)
132 				page_size = 4096;
133 
134 			void *ptr = mmap(NULL, page_size, PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
135 
136 			if (ptr == MAP_FAILED) {
137 				map_jit_flag = MAP_JIT;
138 			} else {
139 				munmap(ptr, page_size);
140 			}
141 		}
142 	}
143 
144 	return map_jit_flag;
145 #else /* !TARGET_OS_OSX */
146 	return MAP_JIT;
147 #endif /* TARGET_OS_OSX */
148 }
149 
150 #endif /* MAP_JIT */
151 
152 #endif /* __APPLE__ */
153 
alloc_chunk(sljit_uw size)154 static SLJIT_INLINE void* alloc_chunk(sljit_uw size)
155 {
156 	void *retval;
157 	const int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
158 
159 #ifdef MAP_ANON
160 
161 	int flags = MAP_PRIVATE | MAP_ANON;
162 
163 #ifdef MAP_JIT
164 	flags |= get_map_jit_flag();
165 #endif
166 
167 	retval = mmap(NULL, size, prot, flags, -1, 0);
168 #else /* !MAP_ANON */
169 	if (dev_zero < 0) {
170 		if (open_dev_zero())
171 			return NULL;
172 	}
173 	retval = mmap(NULL, size, prot, MAP_PRIVATE, dev_zero, 0);
174 #endif /* MAP_ANON */
175 
176 	if (retval == MAP_FAILED)
177 		retval = NULL;
178 	else {
179 		if (mprotect(retval, size, prot) < 0) {
180 			munmap(retval, size);
181 			retval = NULL;
182 		}
183 	}
184 
185 	return retval;
186 }
187 
free_chunk(void * chunk,sljit_uw size)188 static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
189 {
190 	munmap(chunk, size);
191 }
192 
193 #endif
194 
195 /* --------------------------------------------------------------------- */
196 /*  Common functions                                                     */
197 /* --------------------------------------------------------------------- */
198 
199 #define CHUNK_MASK	(~(CHUNK_SIZE - 1))
200 
201 struct block_header {
202 	sljit_uw size;
203 	sljit_uw prev_size;
204 };
205 
206 struct free_block {
207 	struct block_header header;
208 	struct free_block *next;
209 	struct free_block *prev;
210 	sljit_uw size;
211 };
212 
213 #define AS_BLOCK_HEADER(base, offset) \
214 	((struct block_header*)(((sljit_u8*)base) + offset))
215 #define AS_FREE_BLOCK(base, offset) \
216 	((struct free_block*)(((sljit_u8*)base) + offset))
217 #define MEM_START(base)		((void*)(((sljit_u8*)base) + sizeof(struct block_header)))
218 #define ALIGN_SIZE(size)	(((size) + sizeof(struct block_header) + 7) & ~7)
219 
220 static struct free_block* free_blocks;
221 static sljit_uw allocated_size;
222 static sljit_uw total_size;
223 
sljit_insert_free_block(struct free_block * free_block,sljit_uw size)224 static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size)
225 {
226 	free_block->header.size = 0;
227 	free_block->size = size;
228 
229 	free_block->next = free_blocks;
230 	free_block->prev = NULL;
231 	if (free_blocks)
232 		free_blocks->prev = free_block;
233 	free_blocks = free_block;
234 }
235 
sljit_remove_free_block(struct free_block * free_block)236 static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block)
237 {
238 	if (free_block->next)
239 		free_block->next->prev = free_block->prev;
240 
241 	if (free_block->prev)
242 		free_block->prev->next = free_block->next;
243 	else {
244 		SLJIT_ASSERT(free_blocks == free_block);
245 		free_blocks = free_block->next;
246 	}
247 }
248 
sljit_malloc_exec(sljit_uw size)249 SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
250 {
251 	struct block_header *header;
252 	struct block_header *next_header;
253 	struct free_block *free_block;
254 	sljit_uw chunk_size;
255 
256 	allocator_grab_lock();
257 	if (size < (64 - sizeof(struct block_header)))
258 		size = (64 - sizeof(struct block_header));
259 	size = ALIGN_SIZE(size);
260 
261 	free_block = free_blocks;
262 	while (free_block) {
263 		if (free_block->size >= size) {
264 			chunk_size = free_block->size;
265 			if (chunk_size > size + 64) {
266 				/* We just cut a block from the end of the free block. */
267 				chunk_size -= size;
268 				free_block->size = chunk_size;
269 				header = AS_BLOCK_HEADER(free_block, chunk_size);
270 				header->prev_size = chunk_size;
271 				AS_BLOCK_HEADER(header, size)->prev_size = size;
272 			}
273 			else {
274 				sljit_remove_free_block(free_block);
275 				header = (struct block_header*)free_block;
276 				size = chunk_size;
277 			}
278 			allocated_size += size;
279 			header->size = size;
280 			allocator_release_lock();
281 			return MEM_START(header);
282 		}
283 		free_block = free_block->next;
284 	}
285 
286 	chunk_size = (size + sizeof(struct block_header) + CHUNK_SIZE - 1) & CHUNK_MASK;
287 	header = (struct block_header*)alloc_chunk(chunk_size);
288 	if (!header) {
289 		allocator_release_lock();
290 		return NULL;
291 	}
292 
293 	chunk_size -= sizeof(struct block_header);
294 	total_size += chunk_size;
295 
296 	header->prev_size = 0;
297 	if (chunk_size > size + 64) {
298 		/* Cut the allocated space into a free and a used block. */
299 		allocated_size += size;
300 		header->size = size;
301 		chunk_size -= size;
302 
303 		free_block = AS_FREE_BLOCK(header, size);
304 		free_block->header.prev_size = size;
305 		sljit_insert_free_block(free_block, chunk_size);
306 		next_header = AS_BLOCK_HEADER(free_block, chunk_size);
307 	}
308 	else {
309 		/* All space belongs to this allocation. */
310 		allocated_size += chunk_size;
311 		header->size = chunk_size;
312 		next_header = AS_BLOCK_HEADER(header, chunk_size);
313 	}
314 	next_header->size = 1;
315 	next_header->prev_size = chunk_size;
316 	allocator_release_lock();
317 	return MEM_START(header);
318 }
319 
sljit_free_exec(void * ptr)320 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
321 {
322 	struct block_header *header;
323 	struct free_block* free_block;
324 
325 	allocator_grab_lock();
326 	header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header));
327 	allocated_size -= header->size;
328 
329 	/* Connecting free blocks together if possible. */
330 
331 	/* If header->prev_size == 0, free_block will equal to header.
332 	   In this case, free_block->header.size will be > 0. */
333 	free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size);
334 	if (SLJIT_UNLIKELY(!free_block->header.size)) {
335 		free_block->size += header->size;
336 		header = AS_BLOCK_HEADER(free_block, free_block->size);
337 		header->prev_size = free_block->size;
338 	}
339 	else {
340 		free_block = (struct free_block*)header;
341 		sljit_insert_free_block(free_block, header->size);
342 	}
343 
344 	header = AS_BLOCK_HEADER(free_block, free_block->size);
345 	if (SLJIT_UNLIKELY(!header->size)) {
346 		free_block->size += ((struct free_block*)header)->size;
347 		sljit_remove_free_block((struct free_block*)header);
348 		header = AS_BLOCK_HEADER(free_block, free_block->size);
349 		header->prev_size = free_block->size;
350 	}
351 
352 	/* The whole chunk is free. */
353 	if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) {
354 		/* If this block is freed, we still have (allocated_size / 2) free space. */
355 		if (total_size - free_block->size > (allocated_size * 3 / 2)) {
356 			total_size -= free_block->size;
357 			sljit_remove_free_block(free_block);
358 			free_chunk(free_block, free_block->size + sizeof(struct block_header));
359 		}
360 	}
361 
362 	allocator_release_lock();
363 }
364 
sljit_free_unused_memory_exec(void)365 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void)
366 {
367 	struct free_block* free_block;
368 	struct free_block* next_free_block;
369 
370 	allocator_grab_lock();
371 
372 	free_block = free_blocks;
373 	while (free_block) {
374 		next_free_block = free_block->next;
375 		if (!free_block->header.prev_size &&
376 				AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) {
377 			total_size -= free_block->size;
378 			sljit_remove_free_block(free_block);
379 			free_chunk(free_block, free_block->size + sizeof(struct block_header));
380 		}
381 		free_block = next_free_block;
382 	}
383 
384 	SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks));
385 	allocator_release_lock();
386 }
387