Commit | Line | Data |
---|---|---|
587fd086 FP |
1 | /* Heap management routines for GNU Emacs on the Microsoft Windows |
2 | API. Copyright (C) 1994, 2001-2014 Free Software Foundation, Inc. | |
95ed0025 | 3 | |
587fd086 | 4 | This file is part of GNU Emacs. |
95ed0025 | 5 | |
587fd086 FP |
6 | GNU Emacs is free software: you can redistribute it and/or modify |
7 | it under the terms of the GNU General Public License as published by | |
8 | the Free Software Foundation, either version 3 of the License, or | |
9 | (at your option) any later version. | |
95ed0025 | 10 | |
587fd086 FP |
11 | GNU Emacs is distributed in the hope that it will be useful, |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | GNU General Public License for more details. | |
95ed0025 | 15 | |
587fd086 FP |
16 | You should have received a copy of the GNU General Public License |
17 | along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ | |
95ed0025 | 18 | |
9ec0b715 | 19 | /* |
587fd086 | 20 | Geoff Voelker (voelker@cs.washington.edu) 7-29-94 |
95ed0025 RS |
21 | */ |
22 | ||
587fd086 FP |
23 | /* |
24 | Heavily modified by Fabrice Popineau (fabrice.popineau@gmail.com) 28-02-2014 | |
25 | */ | |
26 | ||
27 | /* | |
28 | Memory allocation scheme for w32/w64: | |
29 | ||
30 | - Buffers are mmap'ed using a very simple emulation of mmap/munmap | |
31 | - During the temacs phase: | |
32 | * we use a private heap declared to be stored into the `dumped_data' | |
33 | * unfortunately, this heap cannot be made growable, so the size of | |
34 | blocks it can allocate is limited to (0x80000 - pagesize) | |
35 | * the blocks that are larger than this are allocated from the end | |
36 | of the `dumped_data' array; there are not so many of them. | |
37 | We use a very simple first-fit scheme to reuse those blocks. | |
38 | * we check that the private heap does not cross the area used | |
39 | by the bigger chunks. | |
40 | - During the emacs phase: | |
41 | * we create a private heap for new memory blocks | |
42 | * we make sure that we never free a block that has been dumped. | |
43 | Freeing a dumped block could work in principle, but may prove | |
44 | unreliable if we distribute binaries of emacs.exe: MS does not | |
45 | guarantee that the heap data structures are the same across all | |
46 | versions of their OS, even though the API is available since XP. */ | |
47 | ||
4838e624 | 48 | #include <config.h> |
95ed0025 | 49 | #include <stdio.h> |
6c572f9a | 50 | #include <errno.h> |
95ed0025 | 51 | |
587fd086 | 52 | #include <sys/mman.h> |
501199a3 | 53 | #include "w32common.h" |
489f9371 | 54 | #include "w32heap.h" |
8dfdd41f | 55 | #include "lisp.h" /* for VALMASK */ |
95ed0025 | 56 | |
587fd086 FP |
57 | /* We chose to leave those declarations here. They are used only in |
58 | this file. The RtlCreateHeap is available since XP. It is located | |
59 | in ntdll.dll and is available with the DDK. People often | |
60 | complained that HeapCreate doesn't offer the ability to create a | |
61 | heap at a given place, which we need here, and which RtlCreateHeap | |
62 | provides. We reproduce here the definitions available with the | |
63 | DDK. */ | |
64 | ||
65 | typedef PVOID (WINAPI * RtlCreateHeap_Proc) ( | |
66 | /* _In_ */ ULONG Flags, | |
67 | /* _In_opt_ */ PVOID HeapBase, | |
68 | /* _In_opt_ */ SIZE_T ReserveSize, | |
69 | /* _In_opt_ */ SIZE_T CommitSize, | |
70 | /* _In_opt_ */ PVOID Lock, | |
71 | /* _In_opt_ */ PVOID Parameters | |
72 | ); | |
73 | ||
74 | typedef LONG NTSTATUS; | |
75 | ||
76 | typedef NTSTATUS | |
77 | (NTAPI * PRTL_HEAP_COMMIT_ROUTINE)( | |
78 | IN PVOID Base, | |
79 | IN OUT PVOID *CommitAddress, | |
80 | IN OUT PSIZE_T CommitSize | |
81 | ); | |
82 | ||
83 | typedef struct _RTL_HEAP_PARAMETERS { | |
84 | ULONG Length; | |
85 | SIZE_T SegmentReserve; | |
86 | SIZE_T SegmentCommit; | |
87 | SIZE_T DeCommitFreeBlockThreshold; | |
88 | SIZE_T DeCommitTotalFreeThreshold; | |
89 | SIZE_T MaximumAllocationSize; | |
90 | SIZE_T VirtualMemoryThreshold; | |
91 | SIZE_T InitialCommit; | |
92 | SIZE_T InitialReserve; | |
93 | PRTL_HEAP_COMMIT_ROUTINE CommitRoutine; | |
94 | SIZE_T Reserved[ 2 ]; | |
95 | } RTL_HEAP_PARAMETERS, *PRTL_HEAP_PARAMETERS; | |
96 | ||
97 | /* We reserve space for dumping emacs lisp byte-code inside a static | |
98 | array. By storing it in an array, the generic mechanism in | |
99 | unexecw32.c will be able to dump it without the need to add a | |
100 | special segment to the executable. In order to be able to do this | |
101 | without losing too much space, we need to create a Windows heap at | |
102 | the specific address of the static array. The RtlCreateHeap | |
103 | available inside the NT kernel since XP will do this. It allows to | |
104 | create a non-growable heap at a specific address. So before | |
105 | dumping, we create a non-growable heap at the address of the | |
106 | dumped_data[] array. After dumping, we reuse memory allocated | |
107 | there without being able to free it (but most of it is not meant to | |
108 | be freed anyway), and we use a new private heap for all new | |
109 | allocations. */ | |
110 | ||
111 | unsigned char dumped_data[DUMPED_HEAP_SIZE]; | |
e54c8cd1 | 112 | |
30d2b1c2 | 113 | /* Info for managing our preload heap, which is essentially a fixed size |
587fd086 FP |
114 | data area in the executable. */ |
115 | /* Info for keeping track of our heap. */ | |
95ed0025 RS |
116 | unsigned char *data_region_base = NULL; |
117 | unsigned char *data_region_end = NULL; | |
587fd086 | 118 | static DWORD_PTR committed = 0; |
95ed0025 | 119 | |
587fd086 FP |
120 | /* The maximum block size that can be handled by a non-growable w32 |
121 | heap is limited by the MaxBlockSize value below. | |
122 | ||
123 | This point deserves and explanation. | |
124 | ||
125 | The W32 heap allocator can be used for a growable | |
126 | heap or a non-growable one. | |
127 | ||
128 | A growable heap is not compatible with a fixed base address for the | |
129 | heap. Only a non-growable one is. One drawback of non-growable | |
130 | heaps is that they can hold only objects smaller than a certain | |
131 | size (the one defined below). Most of the largest blocks are GC'ed | |
132 | before dumping. In any case and to be safe, we implement a simple | |
133 | first-fit allocation algorithm starting at the end of the | |
134 | dumped_data[] array like depicted below: | |
95ed0025 | 135 | |
587fd086 FP |
136 | ---------------------------------------------- |
137 | | | | | | |
138 | | Private heap |-> <-| Big chunks | | |
139 | | | | | | |
140 | ---------------------------------------------- | |
141 | ^ ^ ^ | |
142 | dumped_data dumped_data bc_limit | |
143 | + committed | |
144 | ||
145 | */ | |
146 | #define HEAP_ENTRY_SHIFT 3 | |
147 | #define PAGE_SIZE 0x1000 | |
148 | #define MaxBlockSize (0x80000 - PAGE_SIZE) | |
149 | ||
150 | #define MAX_BLOCKS 0x40 | |
151 | ||
152 | static struct | |
95ed0025 | 153 | { |
587fd086 FP |
154 | unsigned char *address; |
155 | size_t size; | |
156 | DWORD occupied; | |
157 | } blocks[MAX_BLOCKS]; | |
158 | ||
159 | static DWORD blocks_number = 0; | |
160 | static unsigned char *bc_limit; | |
161 | ||
162 | /* Handle for the private heap: | |
163 | - inside the dumped_data[] array before dump, | |
164 | - outside of it after dump. | |
165 | */ | |
166 | HANDLE heap = NULL; | |
167 | ||
168 | /* We redirect the standard allocation functions. */ | |
169 | malloc_fn the_malloc_fn; | |
170 | realloc_fn the_realloc_fn; | |
171 | free_fn the_free_fn; | |
95ed0025 | 172 | |
587fd086 FP |
173 | /* It doesn't seem to be useful to allocate from a file mapping. |
174 | It would be if the memory was shared. | |
175 | http://stackoverflow.com/questions/307060/what-is-the-purpose-of-allocating-pages-in-the-pagefile-with-createfilemapping */ | |
176 | ||
177 | /* This is the function to commit memory when the heap allocator | |
178 | claims for new memory. Before dumping, we allocate space | |
179 | from the fixed size dumped_data[] array. | |
180 | */ | |
181 | NTSTATUS NTAPI | |
182 | dumped_data_commit (PVOID Base, PVOID *CommitAddress, PSIZE_T CommitSize) | |
011db670 | 183 | { |
587fd086 FP |
184 | /* This is used before dumping. |
185 | ||
186 | The private heap is stored at dumped_data[] address. | |
187 | We commit contiguous areas of the dumped_data array | |
188 | as requests arrive. */ | |
189 | *CommitAddress = data_region_base + committed; | |
190 | committed += *CommitSize; | |
191 | if (((unsigned char *)(*CommitAddress)) + *CommitSize >= bc_limit) | |
709fd16b | 192 | { |
587fd086 FP |
193 | /* Check that the private heap area does not overlap the big |
194 | chunks area. */ | |
195 | fprintf(stderr, | |
196 | "dumped_data_commit: memory exhausted.\nEnlarge dumped_data[]!\n"); | |
197 | exit (-1); | |
709fd16b | 198 | } |
587fd086 | 199 | return 0; |
011db670 | 200 | } |
587fd086 FP |
201 | |
202 | /* Heap creation. */ | |
203 | ||
904e7cf8 | 204 | /* We want to turn on Low Fragmentation Heap for XP and older systems. |
587fd086 FP |
205 | MinGW32 lacks those definitions. */ |
206 | #ifndef _W64 | |
207 | typedef enum _HEAP_INFORMATION_CLASS { | |
208 | HeapCompatibilityInformation | |
209 | } HEAP_INFORMATION_CLASS; | |
210 | ||
211 | typedef WINBASEAPI BOOL (WINAPI * HeapSetInformation_Proc)(HANDLE,HEAP_INFORMATION_CLASS,PVOID,SIZE_T); | |
212 | #endif | |
213 | ||
214 | void | |
215 | init_heap (void) | |
fab624aa | 216 | { |
587fd086 FP |
217 | if (using_dynamic_heap) |
218 | { | |
219 | unsigned long enable_lfh = 2; | |
220 | ||
221 | /* After dumping, use a new private heap. We explicitly enable | |
e67cf8c6 EZ |
222 | the low fragmentation heap (LFH) here, for the sake of pre |
223 | Vista versions. Note: this will harmlessly fail on Vista and | |
224 | later, where the low-fragmentation heap is enabled by | |
587fd086 FP |
225 | default. It will also fail on pre-Vista versions when Emacs |
226 | is run under a debugger; set _NO_DEBUG_HEAP=1 in the | |
227 | environment before starting GDB to get low fragmentation heap | |
228 | on XP and older systems, for the price of losing "certain | |
229 | heap debug options"; for the details see | |
230 | http://msdn.microsoft.com/en-us/library/windows/desktop/aa366705%28v=vs.85%29.aspx. */ | |
231 | data_region_end = data_region_base; | |
232 | ||
233 | /* Create the private heap. */ | |
234 | heap = HeapCreate(0, 0, 0); | |
235 | ||
236 | #ifndef _W64 | |
904e7cf8 | 237 | /* Set the low-fragmentation heap for OS before Vista. */ |
587fd086 FP |
238 | HMODULE hm_kernel32dll = LoadLibrary("kernel32.dll"); |
239 | HeapSetInformation_Proc s_pfn_Heap_Set_Information = (HeapSetInformation_Proc) GetProcAddress(hm_kernel32dll, "HeapSetInformation"); | |
240 | if (s_pfn_Heap_Set_Information != NULL) | |
241 | if (s_pfn_Heap_Set_Information ((PVOID) heap, | |
242 | HeapCompatibilityInformation, | |
243 | &enable_lfh, sizeof(enable_lfh)) == 0) | |
6c572f9a EZ |
244 | DebPrint (("Enabling Low Fragmentation Heap failed: error %ld\n", |
245 | GetLastError ())); | |
62aba0d4 | 246 | #endif |
fab624aa | 247 | |
587fd086 FP |
248 | the_malloc_fn = malloc_after_dump; |
249 | the_realloc_fn = realloc_after_dump; | |
250 | the_free_fn = free_after_dump; | |
251 | } | |
252 | else | |
fab624aa | 253 | { |
587fd086 FP |
254 | /* Find the RtlCreateHeap function. Headers for this function |
255 | are provided with the w32 ddk, but the function is available | |
256 | in ntdll.dll since XP. */ | |
257 | HMODULE hm_ntdll = LoadLibrary ("ntdll.dll"); | |
258 | RtlCreateHeap_Proc s_pfn_Rtl_Create_Heap | |
259 | = (RtlCreateHeap_Proc) GetProcAddress (hm_ntdll, "RtlCreateHeap"); | |
260 | /* Specific parameters for the private heap. */ | |
261 | RTL_HEAP_PARAMETERS params; | |
262 | ZeroMemory(¶ms, sizeof(params)); | |
263 | params.Length = sizeof(RTL_HEAP_PARAMETERS); | |
264 | ||
265 | data_region_base = (unsigned char *)ROUND_UP (dumped_data, 0x1000); | |
266 | data_region_end = bc_limit = dumped_data + DUMPED_HEAP_SIZE; | |
267 | ||
268 | params.InitialCommit = committed = 0x1000; | |
269 | params.InitialReserve = sizeof(dumped_data); | |
270 | /* Use our own routine to commit memory from the dumped_data | |
271 | array. */ | |
272 | params.CommitRoutine = &dumped_data_commit; | |
273 | ||
274 | /* Create the private heap. */ | |
275 | heap = s_pfn_Rtl_Create_Heap (0, data_region_base, 0, 0, NULL, ¶ms); | |
276 | the_malloc_fn = malloc_before_dump; | |
277 | the_realloc_fn = realloc_before_dump; | |
278 | the_free_fn = free_before_dump; | |
fab624aa EZ |
279 | } |
280 | ||
587fd086 FP |
281 | /* Update system version information to match current system. */ |
282 | cache_system_info (); | |
fab624aa | 283 | } |
011db670 | 284 | |
587fd086 FP |
285 | #undef malloc |
286 | #undef realloc | |
287 | #undef calloc | |
288 | #undef free | |
289 | ||
290 | /* FREEABLE_P checks if the block can be safely freed. */ | |
291 | #define FREEABLE_P(addr) \ | |
292 | ((unsigned char *)(addr) < dumped_data \ | |
293 | || (unsigned char *)(addr) >= dumped_data + DUMPED_HEAP_SIZE) | |
011db670 | 294 | |
95ed0025 | 295 | void * |
587fd086 | 296 | malloc_after_dump (size_t size) |
95ed0025 | 297 | { |
587fd086 FP |
298 | /* Use the new private heap. */ |
299 | void *p = HeapAlloc (heap, 0, size); | |
ce20e03e | 300 | |
0dd0ad37 | 301 | /* After dump, keep track of the "brk value" for sbrk(0). */ |
6c572f9a | 302 | if (p) |
0dd0ad37 EZ |
303 | { |
304 | unsigned char *new_brk = (unsigned char *)p + size; | |
305 | ||
306 | if (new_brk > data_region_end) | |
307 | data_region_end = new_brk; | |
308 | } | |
6c572f9a EZ |
309 | else |
310 | errno = ENOMEM; | |
587fd086 FP |
311 | return p; |
312 | } | |
ce20e03e | 313 | |
587fd086 FP |
314 | void * |
315 | malloc_before_dump (size_t size) | |
316 | { | |
317 | void *p; | |
318 | ||
319 | /* Before dumping. The private heap can handle only requests for | |
320 | less than MaxBlockSize. */ | |
321 | if (size < MaxBlockSize) | |
322 | { | |
323 | /* Use the private heap if possible. */ | |
324 | p = HeapAlloc (heap, 0, size); | |
6c572f9a EZ |
325 | if (!p) |
326 | errno = ENOMEM; | |
587fd086 FP |
327 | } |
328 | else | |
95ed0025 | 329 | { |
587fd086 FP |
330 | /* Find the first big chunk that can hold the requested size. */ |
331 | int i = 0; | |
332 | ||
333 | for (i = 0; i < blocks_number; i++) | |
334 | { | |
335 | if (blocks[i].occupied == 0 && blocks[i].size >= size) | |
336 | break; | |
337 | } | |
338 | if (i < blocks_number) | |
3bbabc43 | 339 | { |
587fd086 FP |
340 | /* If found, use it. */ |
341 | p = blocks[i].address; | |
342 | blocks[i].occupied = TRUE; | |
343 | } | |
344 | else | |
345 | { | |
346 | /* Allocate a new big chunk from the end of the dumped_data | |
347 | array. */ | |
348 | if (blocks_number >= MAX_BLOCKS) | |
349 | { | |
350 | fprintf(stderr, | |
351 | "malloc_before_dump: no more big chunks available.\nEnlarge MAX_BLOCKS!\n"); | |
352 | exit (-1); | |
353 | } | |
354 | bc_limit -= size; | |
355 | bc_limit = (unsigned char *)ROUND_DOWN (bc_limit, 0x10); | |
356 | p = bc_limit; | |
357 | blocks[blocks_number].address = p; | |
358 | blocks[blocks_number].size = size; | |
359 | blocks[blocks_number].occupied = TRUE; | |
360 | blocks_number++; | |
361 | if (bc_limit < dumped_data + committed) | |
362 | { | |
363 | /* Check that areas do not overlap. */ | |
364 | fprintf(stderr, | |
365 | "malloc_before_dump: memory exhausted.\nEnlarge dumped_data[]!\n"); | |
366 | exit (-1); | |
367 | } | |
368 | } | |
369 | } | |
370 | return p; | |
371 | } | |
372 | ||
373 | /* Re-allocate the previously allocated block in ptr, making the new | |
374 | block SIZE bytes long. */ | |
375 | void * | |
376 | realloc_after_dump (void *ptr, size_t size) | |
377 | { | |
378 | void *p; | |
95ed0025 | 379 | |
587fd086 FP |
380 | /* After dumping. */ |
381 | if (FREEABLE_P (ptr)) | |
382 | { | |
383 | /* Reallocate the block since it lies in the new heap. */ | |
384 | p = HeapReAlloc (heap, 0, ptr, size); | |
6c572f9a EZ |
385 | if (!p) |
386 | errno = ENOMEM; | |
ce20e03e | 387 | } |
587fd086 | 388 | else |
95ed0025 | 389 | { |
587fd086 FP |
390 | /* If the block lies in the dumped data, do not free it. Only |
391 | allocate a new one. */ | |
392 | p = HeapAlloc (heap, 0, size); | |
6c572f9a EZ |
393 | if (p) |
394 | CopyMemory (p, ptr, size); | |
395 | else | |
396 | errno = ENOMEM; | |
95ed0025 | 397 | } |
0dd0ad37 | 398 | /* After dump, keep track of the "brk value" for sbrk(0). */ |
6c572f9a | 399 | if (p) |
0dd0ad37 EZ |
400 | { |
401 | unsigned char *new_brk = (unsigned char *)p + size; | |
402 | ||
403 | if (new_brk > data_region_end) | |
404 | data_region_end = new_brk; | |
405 | } | |
587fd086 FP |
406 | return p; |
407 | } | |
ce20e03e | 408 | |
587fd086 FP |
409 | void * |
410 | realloc_before_dump (void *ptr, size_t size) | |
411 | { | |
412 | void *p; | |
413 | ||
414 | /* Before dumping. */ | |
415 | if (dumped_data < (unsigned char *)ptr | |
416 | && (unsigned char *)ptr < bc_limit && size <= MaxBlockSize) | |
6c572f9a EZ |
417 | { |
418 | p = HeapReAlloc (heap, 0, ptr, size); | |
419 | if (!p) | |
420 | errno = ENOMEM; | |
421 | } | |
587fd086 FP |
422 | else |
423 | { | |
424 | /* In this case, either the new block is too large for the heap, | |
425 | or the old block was already too large. In both cases, | |
426 | malloc_before_dump() and free_before_dump() will take care of | |
427 | reallocation. */ | |
428 | p = malloc_before_dump (size); | |
be04283a EZ |
429 | /* If SIZE is below MaxBlockSize, malloc_before_dump will try to |
430 | allocate it in the fixed heap. If that fails, we could have | |
431 | kept the block in its original place, above bc_limit, instead | |
432 | of failing the call as below. But this doesn't seem to be | |
433 | worth the added complexity, as loadup allocates only a very | |
434 | small number of large blocks, and never reallocates them. */ | |
6c572f9a EZ |
435 | if (p) |
436 | { | |
437 | CopyMemory (p, ptr, size); | |
438 | free_before_dump (ptr); | |
439 | } | |
587fd086 FP |
440 | } |
441 | return p; | |
95ed0025 RS |
442 | } |
443 | ||
587fd086 | 444 | /* Free a block allocated by `malloc', `realloc' or `calloc'. */ |
95ed0025 | 445 | void |
587fd086 | 446 | free_after_dump (void *ptr) |
95ed0025 | 447 | { |
587fd086 FP |
448 | /* After dumping. */ |
449 | if (FREEABLE_P (ptr)) | |
450 | { | |
451 | /* Free the block if it is in the new private heap. */ | |
452 | HeapFree (heap, 0, ptr); | |
453 | } | |
454 | } | |
30d2b1c2 | 455 | |
587fd086 FP |
456 | void |
457 | free_before_dump (void *ptr) | |
458 | { | |
459 | /* Before dumping. */ | |
460 | if (dumped_data < (unsigned char *)ptr | |
461 | && (unsigned char *)ptr < bc_limit) | |
30d2b1c2 | 462 | { |
587fd086 FP |
463 | /* Free the block if it is allocated in the private heap. */ |
464 | HeapFree (heap, 0, ptr); | |
465 | } | |
466 | else | |
467 | { | |
468 | /* Look for the big chunk. */ | |
469 | int i; | |
30d2b1c2 | 470 | |
587fd086 | 471 | for(i = 0; i < blocks_number; i++) |
30d2b1c2 | 472 | { |
587fd086 FP |
473 | if (blocks[i].address == ptr) |
474 | { | |
475 | /* Reset block occupation if found. */ | |
476 | blocks[i].occupied = 0; | |
477 | break; | |
478 | } | |
479 | /* What if the block is not found? We should trigger an | |
480 | error here. */ | |
481 | eassert (i < blocks_number); | |
30d2b1c2 | 482 | } |
30d2b1c2 | 483 | } |
587fd086 FP |
484 | } |
485 | ||
d2ff520a EZ |
486 | #ifdef ENABLE_CHECKING |
487 | void | |
488 | report_temacs_memory_usage (void) | |
489 | { | |
490 | /* Emulate 'message', which writes to stderr in non-interactive | |
491 | sessions. */ | |
492 | fprintf (stderr, | |
493 | "Dump memory usage: Heap: %" PRIu64 " Large blocks(%lu): %" PRIu64 "\n", | |
494 | (unsigned long long)committed, blocks_number, | |
495 | (unsigned long long)(dumped_data + DUMPED_HEAP_SIZE - bc_limit)); | |
496 | } | |
497 | #endif | |
498 | ||
587fd086 FP |
499 | /* Emulate getpagesize. */ |
500 | int | |
501 | getpagesize (void) | |
502 | { | |
503 | return sysinfo_cache.dwPageSize; | |
504 | } | |
505 | ||
506 | void * | |
507 | sbrk (ptrdiff_t increment) | |
508 | { | |
0dd0ad37 EZ |
509 | /* data_region_end is the address beyond the last allocated byte. |
510 | The sbrk() function is not emulated at all, except for a 0 value | |
511 | of its parameter. This is needed by the Emacs Lisp function | |
512 | `memory-limit'. */ | |
513 | eassert (increment == 0); | |
587fd086 FP |
514 | return data_region_end; |
515 | } | |
516 | ||
517 | #define MAX_BUFFER_SIZE (512 * 1024 * 1024) | |
518 | ||
519 | /* MMAP allocation for buffers. */ | |
520 | void * | |
521 | mmap_alloc (void **var, size_t nbytes) | |
522 | { | |
523 | void *p = NULL; | |
524 | ||
525 | /* We implement amortized allocation. We start by reserving twice | |
526 | the size requested and commit only the size requested. Then | |
527 | realloc could proceed and use the reserved pages, reallocating | |
528 | only if needed. Buffer shrink would happen only so that we stay | |
529 | in the 2x range. This is a big win when visiting compressed | |
530 | files, where the final size of the buffer is not known in | |
531 | advance, and the buffer is enlarged several times as the data is | |
532 | decompressed on the fly. */ | |
533 | if (nbytes < MAX_BUFFER_SIZE) | |
534 | p = VirtualAlloc (NULL, (nbytes * 2), MEM_RESERVE, PAGE_READWRITE); | |
535 | ||
536 | /* If it fails, or if the request is above 512MB, try with the | |
537 | requested size. */ | |
538 | if (p == NULL) | |
539 | p = VirtualAlloc (NULL, nbytes, MEM_RESERVE, PAGE_READWRITE); | |
540 | ||
541 | if (p != NULL) | |
30d2b1c2 | 542 | { |
587fd086 FP |
543 | /* Now, commit pages for NBYTES. */ |
544 | *var = VirtualAlloc (p, nbytes, MEM_COMMIT, PAGE_READWRITE); | |
30d2b1c2 | 545 | } |
801f68b9 | 546 | |
6c572f9a EZ |
547 | if (!p) |
548 | { | |
549 | if (GetLastError () == ERROR_NOT_ENOUGH_MEMORY) | |
550 | errno = ENOMEM; | |
551 | else | |
552 | { | |
553 | DebPrint (("mmap_alloc: error %ld\n", GetLastError ())); | |
554 | errno = EINVAL; | |
555 | } | |
556 | } | |
587fd086 FP |
557 | |
558 | return *var = p; | |
95ed0025 RS |
559 | } |
560 | ||
95ed0025 | 561 | void |
587fd086 | 562 | mmap_free (void **var) |
95ed0025 | 563 | { |
587fd086 FP |
564 | if (*var) |
565 | { | |
566 | if (VirtualFree (*var, 0, MEM_RELEASE) == 0) | |
6c572f9a | 567 | DebPrint (("mmap_free: error %ld\n", GetLastError ())); |
587fd086 FP |
568 | *var = NULL; |
569 | } | |
570 | } | |
ce20e03e | 571 | |
587fd086 FP |
572 | void * |
573 | mmap_realloc (void **var, size_t nbytes) | |
574 | { | |
575 | MEMORY_BASIC_INFORMATION memInfo, m2; | |
576 | ||
577 | if (*var == NULL) | |
578 | return mmap_alloc (var, nbytes); | |
579 | ||
580 | /* This case happens in init_buffer(). */ | |
581 | if (nbytes == 0) | |
582 | { | |
583 | mmap_free (var); | |
584 | return mmap_alloc (var, nbytes); | |
585 | } | |
586 | ||
587 | if (VirtualQuery (*var, &memInfo, sizeof (memInfo)) == 0) | |
6c572f9a | 588 | DebPrint (("mmap_realloc: VirtualQuery error = %ld\n", GetLastError ())); |
587fd086 FP |
589 | |
590 | /* We need to enlarge the block. */ | |
591 | if (memInfo.RegionSize < nbytes) | |
592 | { | |
593 | if (VirtualQuery (*var + memInfo.RegionSize, &m2, sizeof(m2)) == 0) | |
6c572f9a EZ |
594 | DebPrint (("mmap_realloc: VirtualQuery error = %ld\n", |
595 | GetLastError ())); | |
587fd086 FP |
596 | /* If there is enough room in the current reserved area, then |
597 | commit more pages as needed. */ | |
598 | if (m2.State == MEM_RESERVE | |
599 | && nbytes <= memInfo.RegionSize + m2.RegionSize) | |
600 | { | |
601 | void *p; | |
602 | ||
603 | p = VirtualAlloc (*var + memInfo.RegionSize, | |
604 | nbytes - memInfo.RegionSize, | |
605 | MEM_COMMIT, PAGE_READWRITE); | |
606 | if (!p /* && GetLastError() != ERROR_NOT_ENOUGH_MEMORY */) | |
6c572f9a EZ |
607 | { |
608 | DebPrint (("realloc enlarge: VirtualAlloc error %ld\n", | |
609 | GetLastError ())); | |
610 | errno = ENOMEM; | |
611 | } | |
587fd086 FP |
612 | return *var; |
613 | } | |
614 | else | |
615 | { | |
616 | /* Else we must actually enlarge the block by allocating a | |
617 | new one and copying previous contents from the old to the | |
618 | new one. */ | |
619 | void *old_ptr = *var; | |
620 | ||
621 | if (mmap_alloc (var, nbytes)) | |
622 | { | |
623 | CopyMemory (*var, old_ptr, memInfo.RegionSize); | |
624 | mmap_free (&old_ptr); | |
625 | return *var; | |
626 | } | |
627 | else | |
628 | { | |
629 | /* We failed to enlarge the buffer. */ | |
630 | *var = old_ptr; | |
631 | return NULL; | |
632 | } | |
633 | } | |
634 | } | |
635 | ||
636 | /* If we are shrinking by more than one page... */ | |
637 | if (memInfo.RegionSize > nbytes + getpagesize()) | |
638 | { | |
639 | /* If we are shrinking a lot... */ | |
640 | if ((memInfo.RegionSize / 2) > nbytes) | |
641 | { | |
642 | /* Let's give some memory back to the system and release | |
643 | some pages. */ | |
644 | void *old_ptr = *var; | |
645 | ||
646 | if (mmap_alloc (var, nbytes)) | |
647 | { | |
648 | CopyMemory (*var, old_ptr, nbytes); | |
649 | mmap_free (&old_ptr); | |
650 | return *var; | |
651 | } | |
652 | else | |
653 | { | |
654 | /* In case we fail to shrink, try to go on with the old block. | |
655 | But that means there is a lot of memory pressure. | |
656 | We could also decommit pages. */ | |
657 | *var = old_ptr; | |
658 | return *var; | |
659 | } | |
660 | } | |
661 | ||
662 | /* We still can decommit pages. */ | |
663 | if (VirtualFree (*var + nbytes + get_page_size(), | |
664 | memInfo.RegionSize - nbytes - get_page_size(), | |
665 | MEM_DECOMMIT) == 0) | |
6c572f9a | 666 | DebPrint (("mmap_realloc: VirtualFree error %ld\n", GetLastError ())); |
587fd086 FP |
667 | return *var; |
668 | } | |
ce20e03e | 669 | |
587fd086 FP |
670 | /* Not enlarging, not shrinking by more than one page. */ |
671 | return *var; | |
95ed0025 | 672 | } |