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