1 /****************************************************************************
2 **
3 ** This file is part of GAP, a system for computational discrete algebra.
4 **
5 ** Copyright of GAP belongs to its developers, whose names are too numerous
6 ** to list here. Please refer to the COPYRIGHT file for details.
7 **
8 ** SPDX-License-Identifier: GPL-2.0-or-later
9 **
10 ** This file implements functions and variables related to memory management,
11 ** for use by GASMAN.
12 **
13 */
14
15 #include "sysmem.h"
16
17 #include "stats.h"
18 #include "sysfiles.h"
19 #include "sysopt.h"
20
21 #ifdef GAP_MEM_CHECK
22 #include <fcntl.h>
23 #endif
24
25 #include <unistd.h>
26
27 #ifdef HAVE_MADVISE
28 #include <sys/mman.h>
29 #endif
30
31 #ifdef HAVE_VM_ALLOCATE
32 #include <mach/mach.h>
33 #endif
34
35
36 Int SyStorMax;
37 Int SyStorOverrun;
38 Int SyStorKill;
39 Int SyStorMin;
40
41 #if defined(USE_GASMAN)
42 UInt SyAllocPool;
43 #endif
44
45
46 /****************************************************************************
47 **
48 *F * * * * * * * * * * * * * * gasman interface * * * * * * * * * * * * * * *
49 */
50
51
52 /****************************************************************************
53 **
54 *F SyMsgsBags( <full>, <phase>, <nr> ) . . . . . . . display Gasman messages
55 **
56 ** 'SyMsgsBags' is the function that is used by Gasman to display messages
57 ** during garbage collections.
58 */
59
60 Int SyGasmanNumbers[2][9];
61
SyMsgsBags(UInt full,UInt phase,Int nr)62 void SyMsgsBags (
63 UInt full,
64 UInt phase,
65 Int nr )
66 {
67 Char cmd [3]; /* command string buffer */
68 Char str [32]; /* string buffer */
69 Char ch; /* leading character */
70 UInt i; /* loop variable */
71 Int copynr; /* copy of <nr> */
72 UInt shifted; /* non-zero if nr > 10^6 and so
73 has to be shifted down */
74 static UInt tstart = 0;
75
76 /* remember the numbers */
77 if (phase > 0)
78 {
79 SyGasmanNumbers[full][phase] = nr;
80
81 /* in a full GC clear the partial numbers */
82 if (full)
83 SyGasmanNumbers[0][phase] = 0;
84 }
85 else
86 {
87 SyGasmanNumbers[full][0]++;
88 tstart = SyTime();
89 }
90 if (phase == 6)
91 {
92 UInt x = SyTime() - tstart;
93 SyGasmanNumbers[full][7] = x;
94 SyGasmanNumbers[full][8] += x;
95 }
96
97 /* convert <nr> into a string with leading blanks */
98 copynr = nr;
99 ch = '0'; str[7] = '\0';
100 shifted = (nr >= ((phase % 2) ? 10000000 : 1000000)) ? 1 : 0;
101 if (shifted)
102 {
103 nr /= 1024;
104 }
105 if ((phase % 2) == 1 && shifted && nr > 1000000)
106 {
107 shifted++;
108 nr /= 1024;
109 }
110
111 for ( i = ((phase % 2) == 1 && shifted) ? 6 : 7 ;
112 i != 0; i-- ) {
113 if ( 0 < nr ) { str[i-1] = '0' + ( nr) % 10; ch = ' '; }
114 else if ( nr < 0 ) { str[i-1] = '0' + (-nr) % 10; ch = '-'; }
115 else { str[i-1] = ch; ch = ' '; }
116 nr = nr / 10;
117 }
118 nr = copynr;
119
120 if ((phase % 2) == 1 && shifted == 1)
121 str[6] = 'K';
122 if ((phase % 2) == 1 && shifted == 2)
123 str[6] = 'M';
124
125
126
127 /* ordinary full garbage collection messages */
128 if ( 1 <= SyMsgsFlagBags && full ) {
129 if ( phase == 0 ) { SyFputs( "#G FULL ", 3 ); }
130 if ( phase == 1 ) { SyFputs( str, 3 ); SyFputs( "/", 3 ); }
131 if ( phase == 2 ) { SyFputs( str, 3 ); SyFputs( shifted ? "mb live " : "kb live ", 3 ); }
132 if ( phase == 3 ) { SyFputs( str, 3 ); SyFputs( "/", 3 ); }
133 if ( phase == 4 ) { SyFputs( str, 3 ); SyFputs( shifted ? "mb dead " : "kb dead ", 3 ); }
134 if ( phase == 5 ) { SyFputs( str, 3 ); SyFputs( "/", 3 ); }
135 if ( phase == 6 ) { SyFputs( str, 3 ); SyFputs( shifted ? "mb free\n" : "kb free\n", 3 ); }
136 }
137
138 /* ordinary partial garbage collection messages */
139 if ( 2 <= SyMsgsFlagBags && ! full ) {
140 if ( phase == 0 ) { SyFputs( "#G PART ", 3 ); }
141 if ( phase == 1 ) { SyFputs( str, 3 ); SyFputs( "/", 3 ); }
142 if ( phase == 2 ) { SyFputs( str, 3 ); SyFputs( shifted ? "mb+live ":"kb+live ", 3 ); }
143 if ( phase == 3 ) { SyFputs( str, 3 ); SyFputs( "/", 3 ); }
144 if ( phase == 4 ) { SyFputs( str, 3 ); SyFputs( shifted ? "mb+dead ":"kb+dead ", 3 ); }
145 if ( phase == 5 ) { SyFputs( str, 3 ); SyFputs( "/", 3 ); }
146 if ( phase == 6 ) { SyFputs( str, 3 ); SyFputs( shifted ? "mb free\n":"kb free\n", 3 ); }
147 }
148 /* package (window) mode full garbage collection messages */
149 if ( phase != 0 ) {
150 cmd[0] = '@';
151 cmd[1] = ( full ? '0' : ' ' ) + phase;
152 cmd[2] = '\0';
153 i = 0;
154 for ( ; 0 < nr; nr /=10 )
155 str[i++] = '0' + (nr % 10);
156 str[i++] = '+';
157 str[i++] = '\0';
158 syWinPut( 1, cmd, str );
159 }
160 }
161
162
163 #if defined(USE_GASMAN)
164
165 /****************************************************************************
166 **
167 *f SyAllocBags( <size>, <need> )
168 **
169 ** For UNIX, 'SyAllocBags' calls 'sbrk', which will work on most systems.
170 **
171 ** Note that it may happen that another function has called 'sbrk'
172 ** between two calls to 'SyAllocBags', so that the next allocation will
173 ** not be immediately adjacent to the last one. In this case 'SyAllocBags'
174 ** returns the area to the operating system, and either returns 0 if <need>
175 ** was 0 or aborts GAP if <need> was 1. 'SyAllocBags' will refuse to extend
176 ** the workspace beyond 'SyStorMax' or to reduce it below 'SyStorMin'.
177 */
178
179 static UInt pagesize = 4096; /* Will be initialised if SyAllocPool > 0 */
180
SyRoundUpToPagesize(UInt x)181 static inline UInt SyRoundUpToPagesize(UInt x)
182 {
183 UInt r;
184 r = x % pagesize;
185 return r == 0 ? x : x - r + pagesize;
186 }
187
188 static void * POOL = NULL;
189 static UInt *** syWorkspace = NULL;
190 static UInt syWorksize = 0;
191
EndOfWorkspace(void)192 static inline UInt *** EndOfWorkspace(void)
193 {
194 return syWorkspace + syWorksize * (1024 / sizeof(UInt **));
195 }
196
197 #ifdef GAP_MEM_CHECK
198
199 /***************************************************************
200 * GAP_MEM_CHECK
201 *
202 * The following code is used by GAP_MEM_CHECK support, which is
203 * documented in gasman.c
204 */
205
206 #if !defined(HAVE_MADVISE) || !defined(SYS_IS_64_BIT)
207 #error GAP_MEM_CHECK requires MADVISE and 64-bit OS
208 #endif
209
SyMAdviseFree(void)210 void SyMAdviseFree(void)
211 {
212 }
213
214
215 enum { membufcount = 64 };
216 static void * membufs[membufcount];
217 static UInt membufSize;
218
GetMembufCount(void)219 UInt GetMembufCount(void)
220 {
221 return membufcount;
222 }
223
GetMembuf(UInt i)224 void * GetMembuf(UInt i)
225 {
226 return membufs[i];
227 }
228
GetMembufSize(void)229 UInt GetMembufSize(void)
230 {
231 return membufSize;
232 }
233
order_pointers(const void * a,const void * b)234 static int order_pointers(const void * a, const void * b)
235 {
236 void * const * pa = a;
237 void * const * pb = b;
238 if (*pa < *pb) {
239 return -1;
240 }
241 else if (*pa > *pb) {
242 return 1;
243 }
244 else {
245 return 0;
246 }
247 }
248
SyAnonMMap(size_t size)249 static void * SyAnonMMap(size_t size)
250 {
251 size = SyRoundUpToPagesize(size);
252 membufSize = size;
253
254 unlink("/dev/shm/gapmem");
255 int fd = open("/dev/shm/gapmem", O_RDWR | O_CREAT | O_EXCL, 0600);
256 if (fd < 0) {
257 Panic("Fatal error setting up multiheap");
258 }
259
260 if (ftruncate(fd, size) < 0) {
261 Panic("Fatal error setting up multiheap!");
262 }
263
264 for (int i = 0; i < membufcount; ++i) {
265 membufs[i] =
266 mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
267 if (membufs[i] == MAP_FAILED) {
268 Panic("Fatal error setting up multiheap!!");
269 }
270 }
271
272 // Sort the membufs, so membufs[0] is the first in memory.
273 // We will always refer to the copy of the master pointers in
274 // membufs[0].
275 qsort(membufs, membufcount, sizeof(void *), order_pointers);
276 return membufs[0];
277 }
278
SyTryToIncreasePool(void)279 int SyTryToIncreasePool(void)
280 {
281 return -1;
282 }
283
284 #elif defined(HAVE_MADVISE)
285
286 #ifndef MAP_ANONYMOUS
287 #define MAP_ANONYMOUS MAP_ANON
288 #endif
289
290 #ifdef SYS_IS_CYGWIN32
291 #define GAP_MMAP_FLAGS MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE
292 #else
293 #define GAP_MMAP_FLAGS MAP_PRIVATE|MAP_ANONYMOUS
294 #endif
295
296 static void *SyMMapStart = NULL; /* Start of mmap'ed region for POOL */
297 static void *SyMMapEnd; /* End of mmap'ed region for POOL */
298 static void *SyMMapAdvised; /* We have already advised about non-usage
299 up to here. */
300
SyMAdviseFree(void)301 void SyMAdviseFree(void) {
302 size_t size;
303 void *from;
304 if (!SyMMapStart)
305 return;
306 from = EndOfWorkspace();
307 from = (void *)SyRoundUpToPagesize((UInt) from);
308 if (from > SyMMapAdvised) {
309 SyMMapAdvised = from;
310 return;
311 }
312 if (from < SyMMapStart || from >= SyMMapEnd || from >= SyMMapAdvised)
313 return;
314 size = (char *)SyMMapAdvised - (char *)from;
315 #if defined(MADV_FREE)
316 madvise(from, size, MADV_FREE);
317 #elif defined(MADV_DONTNEED)
318 madvise(from, size, MADV_DONTNEED);
319 #endif
320 SyMMapAdvised = from;
321 /* On Darwin, MADV_FREE and MADV_DONTNEED will not actually update
322 * a process's resident memory until those pages are explicitly
323 * unmapped or needed elsewhere.
324 *
325 * The following code accomplishes this, but is not portable and
326 * potentially not safe, since the POSIX standard does not make
327 * any sufficiently strong promises with regard to the use of
328 * MAP_FIXED.
329 *
330 * We probably don't want to do this and just live with pages
331 * remaining with a process until reused even if that appears to
332 * inflate the resident set size.
333 *
334 * Maybe we do want to do this until it breaks to avoid questions
335 * by users...
336 */
337 #if !defined(NO_DIRTY_OSX_MMAP_TRICK) && defined(SYS_IS_DARWIN)
338 if (mmap(from, size, PROT_NONE,
339 MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) != from) {
340 Panic("OS X trick to free pages did not work!");
341 }
342 if (mmap(from, size, PROT_READ|PROT_WRITE,
343 MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) != from) {
344 Panic("OS X trick to free pages did not work!!");
345 }
346 #endif
347 }
348
SyAnonMMap(size_t size)349 static void * SyAnonMMap(size_t size)
350 {
351 void *result;
352 size = SyRoundUpToPagesize(size);
353 #ifdef SYS_IS_64_BIT
354 /* The following is at 16 Terabyte: */
355 result = mmap((void *) (16L*1024*1024*1024*1024), size,
356 PROT_READ|PROT_WRITE, GAP_MMAP_FLAGS, -1, 0);
357 if (result == MAP_FAILED) {
358 result = mmap(NULL, size, PROT_READ|PROT_WRITE,
359 GAP_MMAP_FLAGS, -1, 0);
360 }
361 #else
362 result = mmap(NULL, size, PROT_READ|PROT_WRITE,
363 GAP_MMAP_FLAGS, -1, 0);
364 #endif
365 if (result == MAP_FAILED)
366 result = NULL;
367 SyMMapStart = result;
368 SyMMapEnd = (char *)result + size;
369 SyMMapAdvised = (char *)result + size;
370 return result;
371 }
372
SyTryToIncreasePool(void)373 static int SyTryToIncreasePool(void)
374 /* This tries to increase the pool size by a factor of 3/2, if this
375 * worked, then 0 is returned, otherwise -1. */
376 {
377 void *result;
378 size_t size;
379 size_t newchunk;
380
381 size = (Int) SyMMapEnd - (Int) SyMMapStart;
382 newchunk = SyRoundUpToPagesize(size/2);
383 result = mmap(SyMMapEnd, newchunk, PROT_READ|PROT_WRITE,
384 GAP_MMAP_FLAGS, -1, 0);
385 if (result == MAP_FAILED) return -1;
386 if (result != SyMMapEnd) {
387 munmap(result,newchunk);
388 return -1;
389 }
390 /* We actually got an extension! */
391 SyMMapEnd = (void *)((char *)SyMMapEnd + newchunk);
392 SyAllocPool += newchunk;
393 return 0;
394 }
395
396 #else
397
SyMAdviseFree(void)398 static void SyMAdviseFree(void)
399 {
400 /* do nothing */
401 }
402
SyTryToIncreasePool(void)403 static int SyTryToIncreasePool(void)
404 {
405 return -1; /* Refuse */
406 }
407
408 #endif // defined(GAP_MEM_CHECK)
409
410
411 static int halvingsdone = 0;
412
SyInitialAllocPool(void)413 static void SyInitialAllocPool(void)
414 {
415 #ifdef HAVE_SYSCONF
416 #ifdef _SC_PAGESIZE
417 pagesize = sysconf(_SC_PAGESIZE);
418 #endif
419 #endif
420 /* Otherwise we take the default of 4k as pagesize. */
421
422 do {
423 /* Always round up to pagesize: */
424 SyAllocPool = SyRoundUpToPagesize(SyAllocPool);
425 #ifdef HAVE_MADVISE
426 POOL = SyAnonMMap(SyAllocPool+pagesize); /* For alignment */
427 #else
428 POOL = calloc(SyAllocPool+pagesize,1); /* For alignment */
429 #endif
430 if (POOL != NULL) {
431 /* fprintf(stderr,"Pool size is %lx.\n",SyAllocPool); */
432 break;
433 }
434 SyAllocPool = SyAllocPool / 2;
435 halvingsdone++;
436 if (SyDebugLoading) fputs("gap: halving pool size.\n", stderr);
437 if (SyAllocPool < 16*1024*1024) {
438 Panic("cannot allocate initial memory");
439 }
440 } while (1); /* Is left by break */
441
442 /* ensure alignment of start address */
443 syWorkspace = (UInt***)(SyRoundUpToPagesize((UInt) POOL));
444 /* Now both syWorkspace and SyAllocPool are aligned to pagesize */
445 }
446
SyAllocBagsFromPool(Int size,UInt need)447 static UInt *** SyAllocBagsFromPool(Int size, UInt need)
448 {
449 /* get the storage, but only if we stay within the bounds */
450 /* if ( (0 < size && syWorksize + size <= SyStorMax) */
451 /* first check if we would get above SyStorKill, if yes exit! */
452 if ( need < 2 && SyStorKill != 0 && 0 < size
453 && SyStorKill < syWorksize + size ) {
454 Panic("will not extend workspace above -K limit!");
455 }
456 if (size > 0) {
457 while ((syWorksize+size)*1024 > SyAllocPool) {
458 if (SyTryToIncreasePool()) return (UInt***)-1;
459 }
460 return EndOfWorkspace();
461 }
462 else if (size < 0 && (need >= 2 || SyStorMin <= syWorksize + size))
463 return EndOfWorkspace();
464 else
465 return (UInt***)-1;
466 }
467
468 #if defined(HAVE_SBRK) && !defined(HAVE_VM_ALLOCATE) /* prefer `vm_allocate' over `sbrk' */
469
SyAllocBags(Int size,UInt need)470 UInt *** SyAllocBags(Int size, UInt need)
471 {
472 UInt * * * ret;
473 UInt adjust = 0;
474
475 if (SyAllocPool > 0) {
476 if (POOL == NULL) SyInitialAllocPool();
477 /* Note that this does abort GAP if it does not succeed! */
478
479 ret = SyAllocBagsFromPool(size,need);
480 }
481 else {
482
483
484
485 /* force alignment on first call */
486 if ( syWorkspace == (UInt***)0 ) {
487 #ifdef SYS_IS_64_BIT
488 syWorkspace = (UInt***)sbrk( 8 - (UInt)sbrk(0) % 8 );
489 #else
490 syWorkspace = (UInt***)sbrk( 4 - (UInt)sbrk(0) % 4 );
491 #endif
492 syWorkspace = (UInt***)sbrk( 0 );
493 }
494
495 /* get the storage, but only if we stay within the bounds */
496 /* if ( (0 < size && syWorksize + size <= SyStorMax) */
497 /* first check if we would get above SyStorKill, if yes exit! */
498 if ( need < 2 && SyStorKill != 0 && 0 < size &&
499 SyStorKill < syWorksize + size ) {
500 Panic("will not extend workspace above -K limit!");
501 }
502 if (0 < size )
503 {
504 #ifndef SYS_IS_64_BIT
505 while (size > 1024*1024)
506 {
507 ret = (UInt ***)sbrk(1024*1024*1024);
508 if (ret != (UInt ***)-1 &&
509 ret != EndOfWorkspace())
510 {
511 sbrk(-1024*1024*1024);
512 ret = (UInt ***)-1;
513 }
514 if (ret == (UInt ***)-1)
515 break;
516 memset(EndOfWorkspace(), 0, 1024*1024*1024);
517 size -= 1024*1024;
518 syWorksize += 1024*1024;
519 adjust++;
520 }
521 #endif
522 ret = (UInt ***)sbrk(size*1024);
523 if (ret != (UInt ***)-1 &&
524 ret != EndOfWorkspace())
525 {
526 sbrk(-size*1024);
527 ret = (UInt ***)-1;
528 }
529 if (ret != (UInt ***)-1)
530 memset(EndOfWorkspace(), 0,
531 1024*size);
532
533 }
534 else if (size < 0 && (need >= 2 || SyStorMin <= syWorksize + size)) {
535 #ifndef SYS_IS_64_BIT
536 while (size < -1024*1024)
537 {
538 ret = (UInt ***)sbrk(-1024*1024*1024);
539 if (ret == (UInt ***)-1)
540 break;
541 size += 1024*1024;
542 syWorksize -= 1024*1024;
543 }
544 #endif
545 ret = (UInt ***)sbrk(size*1024);
546 }
547 else {
548 ret = (UInt***)-1;
549 }
550 }
551
552
553 /* update the size info */
554 if ( ret != (UInt***)-1 ) {
555 syWorksize += size;
556 /* set the overrun flag if we became larger than SyStorMax */
557 if ( SyStorMax != 0 && syWorksize > SyStorMax) {
558 SyStorOverrun = -1;
559 SyStorMax=syWorksize*2; /* new maximum */
560 InterruptExecStat(); /* interrupt at the next possible point */
561 }
562 }
563
564 /* test if the allocation failed */
565 if ( ret == (UInt***)-1 && need ) {
566 Panic("cannot extend the workspace any more!");
567 }
568 /* if we de-allocated the whole workspace then remember this */
569 if (syWorksize == 0)
570 syWorkspace = (UInt ***)0;
571
572 /* otherwise return the result (which could be 0 to indicate failure) */
573 if ( ret == (UInt***)-1 )
574 return 0;
575 else
576 {
577 return (UInt***)(((Char *)ret) - 1024*1024*1024*adjust);
578 }
579
580 }
581
582 #endif
583
584
585 /****************************************************************************
586 **
587 *f SyAllocBags( <size>, <need> )
588 **
589 ** Under MACH virtual memory managment functions are used instead of 'sbrk'.
590 */
591 #ifdef HAVE_VM_ALLOCATE
592
593 #if (defined(SYS_IS_DARWIN) && SYS_IS_DARWIN) || defined(__gnu_hurd__)
594 #define task_self mach_task_self
595 #endif
596
597 static vm_address_t syBase;
598
SyAllocBags(Int size,UInt need)599 UInt * * * SyAllocBags (
600 Int size,
601 UInt need )
602 {
603 UInt * * * ret = (UInt***)-1;
604 vm_address_t adr;
605
606 if (SyAllocPool > 0) {
607 if (POOL == NULL) SyInitialAllocPool();
608 /* Note that this does abort GAP if it does not succeed! */
609
610 ret = SyAllocBagsFromPool(size,need);
611 if (ret != (UInt ***)-1)
612 syWorksize += size;
613
614 }
615 else {
616 if ( SyStorKill != 0 && 0 < size && SyStorKill < 1024*(syWorksize + size) ) {
617 if (need) {
618 Panic("will not extend workspace above -K limit!");
619 }
620 }
621 /* check that <size> is divisible by <vm_page_size> */
622 else if ( size*1024 % vm_page_size != 0 ) {
623 Panic("memory block size is not a multiple of vm_page_size");
624 }
625
626 /* check that we don't try to shrink uninitialized memory */
627 else if ( size <= 0 && syBase == 0 ) {
628 Panic("trying to shrink uninitialized vm memory");
629 }
630
631 /* allocate memory anywhere on first call */
632 else if ( 0 < size && syBase == 0 ) {
633 if ( vm_allocate(task_self(),&syBase,size*1024,TRUE) == KERN_SUCCESS ) {
634 syWorksize = size;
635 ret = (UInt***) syBase;
636 }
637 }
638
639 /* don't shrink memory but mark it as deactivated */
640 else if ( size < 0 && syWorksize + size > SyStorMin) {
641 adr = (vm_address_t)( (char*) syBase + (syWorksize+size)*1024 );
642 if ( vm_deallocate(task_self(),adr,-size*1024) == KERN_SUCCESS ) {
643 ret = (UInt***)( (char*) syBase + syWorksize*1024 );
644 syWorksize += size;
645 }
646 }
647
648 /* get more memory from system */
649 else {
650 adr = (vm_address_t)( (char*) syBase + syWorksize*1024 );
651 if ( vm_allocate(task_self(),&adr,size*1024,FALSE) == KERN_SUCCESS ) {
652 ret = (UInt***) ( (char*) syBase + syWorksize*1024 );
653 syWorksize += size;
654 }
655 }
656
657 /* test if the allocation failed */
658 if ( ret == (UInt***)-1 && need ) {
659 Panic("cannot extend the workspace any more!!");
660 }
661 }
662
663 /* otherwise return the result (which could be 0 to indicate failure) */
664 if ( ret == (UInt***)-1 ){
665 if (need) {
666 Panic("cannot extend the workspace any more!!!");
667 }
668 return (UInt***) 0;
669 }
670 else {
671 if (syWorksize > SyStorMax) {
672 SyStorOverrun = -1;
673 SyStorMax=syWorksize*2; /* new maximum */
674 InterruptExecStat(); /* interrupt at the next possible point */
675 }
676 }
677 return ret;
678 }
679
680 #endif
681
682
683 #endif // defined(USE_GASMAN)
684