2004-10-26 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mono / utils / mono-codeman.c
1 #include "config.h"
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <assert.h>
6
7 #ifdef PLATFORM_WIN32
8 #include <windows.h>
9 #include <io.h>
10 #else
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <sys/mman.h>
14 #include <fcntl.h>
15 #endif
16
17 #include "mono-codeman.h"
18
19 #ifdef PLATFORM_WIN32
20 #define FORCE_MALLOC
21 #endif
22
23 #define MIN_PAGES 8
24 #define MIN_ALIGN 8
25 /* if a chunk has less than this amount of free space it's considered full */
26 #define MAX_WASTAGE 32
27
28 #ifndef MAP_ANONYMOUS
29 #ifdef MAP_ANON
30 #define MAP_ANONYMOUS MAP_ANON
31 #else
32 #define FORCE_MALLOC
33 #endif
34 #endif
35
36 typedef struct _CodeChunck CodeChunk;
37
38 enum {
39         CODE_FLAG_MMAP,
40         CODE_FLAG_MALLOC
41 };
42
43 struct _CodeChunck {
44         char *data;
45         int pos;
46         int size;
47         CodeChunk *next;
48         unsigned int flags: 8;
49         /* this number of bytes is available to resolve addresses far in memory */
50         unsigned int bsize: 24;
51 };
52
53 struct _MonoCodeManager {
54         int dynamic;
55         CodeChunk *current;
56         CodeChunk *full;
57 };
58
59 MonoCodeManager* 
60 mono_code_manager_new (void)
61 {
62         MonoCodeManager *cman = malloc (sizeof (MonoCodeManager));
63         if (!cman)
64                 return NULL;
65         cman->current = NULL;
66         cman->full = NULL;
67         cman->dynamic = 0;
68         return cman;
69 }
70
71 MonoCodeManager* 
72 mono_code_manager_new_dynamic (void)
73 {
74         MonoCodeManager *cman = mono_code_manager_new ();
75         cman->dynamic = 1;
76         return cman;
77 }
78
79
80 static void
81 free_chunklist (CodeChunk *chunk)
82 {
83         CodeChunk *dead;
84         for (; chunk; ) {
85                 dead = chunk;
86                 chunk = chunk->next;
87                 if (dead->flags == CODE_FLAG_MMAP) {
88 #ifndef FORCE_MALLOC
89                         munmap (dead->data, dead->size);
90 #endif
91                 } else if (dead->flags == CODE_FLAG_MALLOC) {
92                         free (dead->data);
93                 }
94                 free (dead);
95         }
96 }
97
98 void
99 mono_code_manager_destroy (MonoCodeManager *cman)
100 {
101         free_chunklist (cman->full);
102         free_chunklist (cman->current);
103         free (cman);
104 }
105
106 /* fill all the memory with the 0x2a (42) value */
107 void             
108 mono_code_manager_invalidate (MonoCodeManager *cman)
109 {
110         CodeChunk *chunk;
111
112 #if defined(__i386__) || defined(__x86_64__)
113         int fill_value = 0xcc; /* x86 break */
114 #else
115         int fill_value = 0x2a;
116 #endif
117
118         for (chunk = cman->current; chunk; chunk = chunk->next)
119                 memset (chunk->data, fill_value, chunk->size);
120         for (chunk = cman->full; chunk; chunk = chunk->next)
121                 memset (chunk->data, fill_value, chunk->size);
122 }
123
124 void
125 mono_code_manager_foreach (MonoCodeManager *cman, MonoCodeManagerFunc func, void *user_data)
126 {
127         CodeChunk *chunk;
128         for (chunk = cman->current; chunk; chunk = chunk->next) {
129                 if (func (chunk->data, chunk->size, chunk->bsize, user_data))
130                         return;
131         }
132         for (chunk = cman->full; chunk; chunk = chunk->next) {
133                 if (func (chunk->data, chunk->size, chunk->bsize, user_data))
134                         return;
135         }
136 }
137
138 static int
139 query_pagesize (void)
140 {
141 #ifdef PLATFORM_WIN32
142         SYSTEM_INFO info;
143         GetSystemInfo (&info);
144         return info.dwAllocationGranularity;
145 #else
146         return getpagesize ();
147 #endif
148 }
149
150 /* BIND_ROOM is the divisor for the chunck of code size dedicated
151  * to binding branches (branches not reachable with the immediate displacement)
152  * bind_size = size/BIND_ROOM;
153  * we should reduce it and make MIN_PAGES bigger for such systems
154  */
155 #if defined(__ppc__) || defined(__powerpc__)
156 #define BIND_ROOM 4
157 #endif
158
159 static CodeChunk*
160 new_codechunk (int dynamic, int size)
161 {
162         static int pagesize = 0;
163         int minsize, flags = CODE_FLAG_MMAP;
164         int chunk_size, bsize = 0;
165         CodeChunk *chunk;
166         void *ptr;
167
168 #ifdef FORCE_MALLOC
169         flags = CODE_FLAG_MALLOC;
170 #endif
171
172         if (dynamic) {
173                 chunk_size = size;
174                 flags = CODE_FLAG_MALLOC;
175         }
176         else {
177                 if (!pagesize)
178                         pagesize = query_pagesize ();
179
180                 minsize = pagesize * MIN_PAGES;
181                 if (size < minsize)
182                         chunk_size = minsize;
183                 else {
184                         chunk_size = size;
185                         chunk_size += pagesize - 1;
186                         chunk_size &= ~ (pagesize - 1);
187                 }
188         }
189 #ifdef BIND_ROOM
190         bsize = chunk_size / BIND_ROOM;
191         if (chunk_size - size < bsize) {
192                 if (dynamic)
193                         chunk_size = size + bsize;
194                 else
195                         chunk_size += pagesize;
196         }
197 #endif
198
199         /* does it make sense to use the mmap-like API? */
200         if (flags == CODE_FLAG_MALLOC) {
201                 ptr = malloc (chunk_size);
202                 if (!ptr)
203                         return NULL;
204
205         }
206         else {
207 #ifndef FORCE_MALLOC
208                 ptr = mmap (0, chunk_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
209                 if (ptr == (void*)-1) {
210                         int fd = open ("/dev/zero", O_RDONLY);
211                         if (fd != -1) {
212                                 ptr = mmap (0, chunk_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, fd, 0);
213                                 close (fd);
214                         }
215                         if (ptr == (void*)-1) {
216                                 ptr = malloc (chunk_size);
217                                 if (!ptr)
218                                         return NULL;
219                                 flags = CODE_FLAG_MALLOC;
220                         }
221                 }
222 #else
223                 return NULL;
224 #endif
225         }
226
227         if (flags == CODE_FLAG_MALLOC) {
228                 /*
229                  * AMD64 processors maintain icache coherency only for pages which are 
230                  * marked executable.
231                  */
232 #ifndef PLATFORM_WIN32
233                 {
234                         char *page_start = (char *) (((unsigned long long) (ptr)) & ~ (pagesize - 1));
235                         int pages = ((char*)ptr + chunk_size - page_start + pagesize - 1) / pagesize;
236                         int err = mprotect (page_start, pages * pagesize, PROT_READ | PROT_WRITE | PROT_EXEC);
237                         assert (!err);
238                 }
239 #else
240                 {
241                         DWORD oldp;
242                         int err = VirtualProtect (ptr, chunk_size, PAGE_EXECUTE_READWRITE, &oldp);
243                         assert (err);
244                 }
245 #endif
246
247                         /* Make sure the thunks area is zeroed */
248                         memset (ptr, 0, bsize);
249         }
250
251         chunk = malloc (sizeof (CodeChunk));
252         if (!chunk) {
253                 if (flags == CODE_FLAG_MALLOC)
254                         free (ptr);
255 #ifndef FORCE_MALLOC
256                 else
257                         munmap (ptr, chunk_size);
258 #endif
259                 return NULL;
260         }
261         chunk->next = NULL;
262         chunk->size = chunk_size;
263         chunk->data = ptr;
264         chunk->flags = flags;
265         chunk->pos = bsize;
266         chunk->bsize = bsize;
267
268         /*printf ("code chunk at: %p\n", ptr);*/
269         return chunk;
270 }
271
272 void*
273 mono_code_manager_reserve (MonoCodeManager *cman, int size)
274 {
275         CodeChunk *chunk, *prev;
276         void *ptr;
277         
278         size += MIN_ALIGN;
279         size &= ~ (MIN_ALIGN - 1);
280
281         if (!cman->current) {
282                 cman->current = new_codechunk (cman->dynamic, size);
283                 if (!cman->current)
284                         return NULL;
285         }
286
287         for (chunk = cman->current; chunk; chunk = chunk->next) {
288                 if (chunk->pos + size <= chunk->size) {
289                         ptr = chunk->data + chunk->pos;
290                         chunk->pos += size;
291                         return ptr;
292                 }
293         }
294         /* 
295          * no room found, move one filled chunk to cman->full 
296          * to keep cman->current from growing too much
297          */
298         prev = NULL;
299         for (chunk = cman->current; chunk; prev = chunk, chunk = chunk->next) {
300                 if (chunk->pos + MIN_ALIGN * 4 <= chunk->size)
301                         continue;
302                 if (prev) {
303                         prev->next = chunk->next;
304                 } else {
305                         cman->current = chunk->next;
306                 }
307                 chunk->next = cman->full;
308                 cman->full = chunk;
309                 break;
310         }
311         chunk = new_codechunk (cman->dynamic, size);
312         if (!chunk)
313                 return NULL;
314         chunk->next = cman->current;
315         cman->current = chunk;
316         chunk->pos += size;
317         return chunk->data;
318 }
319
320 /* 
321  * if we reserved too much room for a method and we didn't allocate
322  * already from the code manager, we can get back the excess allocation.
323  */
324 void
325 mono_code_manager_commit (MonoCodeManager *cman, void *data, int size, int newsize)
326 {
327         newsize += MIN_ALIGN;
328         newsize &= ~ (MIN_ALIGN - 1);
329         size += MIN_ALIGN;
330         size &= ~ (MIN_ALIGN - 1);
331
332         if (cman->current && (size != newsize) && (data == cman->current->data + cman->current->pos - size)) {
333                 cman->current->pos -= size - newsize;
334         }
335 }
336