xref: /netbsd-src/external/gpl3/gcc/dist/libphobos/libdruntime/core/internal/gc/os.d (revision 0a3071956a3a9fdebdbf7f338cf2d439b45fc728)
1 /**
2  * Contains OS-level routines needed by the garbage collector.
3  *
4  * Copyright: D Language Foundation 2005 - 2021.
5  * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6  * Authors:   Walter Bright, David Friedman, Sean Kelly, Leandro Lucarella
7  */
8 module core.internal.gc.os;
9 
10 
version(Windows)11 version (Windows)
12 {
13     import core.sys.windows.winbase : GetCurrentThreadId, VirtualAlloc, VirtualFree;
14     import core.sys.windows.winnt : MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE;
15 
16     alias int pthread_t;
17 
18     pthread_t pthread_self() nothrow
19     {
20         return cast(pthread_t) GetCurrentThreadId();
21     }
22 
23     //version = GC_Use_Alloc_Win32;
24 }
version(Posix)25 else version (Posix)
26 {
27     version (OSX)
28         version = Darwin;
29     else version (iOS)
30         version = Darwin;
31     else version (TVOS)
32         version = Darwin;
33     else version (WatchOS)
34         version = Darwin;
35 
36     import core.sys.posix.sys.mman;
37     import core.stdc.stdlib;
38 
39 
40     /// Possible results for the wait_pid() function.
41     enum ChildStatus
42     {
43         done, /// The process has finished successfully
44         running, /// The process is still running
45         error /// There was an error waiting for the process
46     }
47 
48     /**
49      * Wait for a process with PID pid to finish.
50      *
51      * If block is false, this function will not block, and return ChildStatus.running if
52      * the process is still running. Otherwise it will return always ChildStatus.done
53      * (unless there is an error, in which case ChildStatus.error is returned).
54      */
55     ChildStatus wait_pid(pid_t pid, bool block = true) nothrow @nogc
56     {
57         import core.exception : onForkError;
58 
59         int status = void;
60         pid_t waited_pid = void;
61         // In the case where we are blocking, we need to consider signals
62         // arriving while we wait, and resume the waiting if EINTR is returned
63         do {
64             errno = 0;
65             waited_pid = waitpid(pid, &status, block ? 0 : WNOHANG);
66         }
67         while (waited_pid == -1 && errno == EINTR);
68         if (waited_pid == 0)
69             return ChildStatus.running;
70         else if (errno ==  ECHILD)
71             return ChildStatus.done; // someone called posix.syswait
72         else if (waited_pid != pid || status != 0)
73         {
74             onForkError();
75             return ChildStatus.error;
76         }
77         return ChildStatus.done;
78     }
79 
80     public import core.sys.posix.unistd: pid_t, fork;
81     import core.sys.posix.sys.wait: waitpid, WNOHANG;
82     import core.stdc.errno: errno, EINTR, ECHILD;
83 
84     //version = GC_Use_Alloc_MMap;
85 }
86 else
87 {
88     import core.stdc.stdlib;
89 
90     //version = GC_Use_Alloc_Malloc;
91 }
92 
93 /+
94 static if (is(typeof(VirtualAlloc)))
95     version = GC_Use_Alloc_Win32;
96 else static if (is(typeof(mmap)))
97     version = GC_Use_Alloc_MMap;
98 else static if (is(typeof(valloc)))
99     version = GC_Use_Alloc_Valloc;
100 else static if (is(typeof(malloc)))
101     version = GC_Use_Alloc_Malloc;
102 else static assert(false, "No supported allocation methods available.");
103 +/
104 
105 static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32)
106 {
107     /**
108     * Indicates if an implementation supports fork().
109     *
110     * The value shown here is just demostrative, the real value is defined based
111     * on the OS it's being compiled in.
112     * enum HaveFork = true;
113     */
114     enum HaveFork = false;
115 
116     /**
117      * Map memory.
118      */
os_mem_map(size_t nbytes)119     void *os_mem_map(size_t nbytes) nothrow @nogc
120     {
121         return VirtualAlloc(null, nbytes, MEM_RESERVE | MEM_COMMIT,
122                 PAGE_READWRITE);
123     }
124 
125 
126     /**
127      * Unmap memory allocated with os_mem_map().
128      * Returns:
129      *      0       success
130      *      !=0     failure
131      */
os_mem_unmap(void * base,size_t nbytes)132     int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc
133     {
134         return cast(int)(VirtualFree(base, 0, MEM_RELEASE) == 0);
135     }
136 }
137 else static if (is(typeof(mmap)))  // else version (GC_Use_Alloc_MMap)
138 {
139     enum HaveFork = true;
140 
141     void *os_mem_map(size_t nbytes, bool share = false) nothrow @nogc
142     {   void *p;
143 
144         auto map_f = share ? MAP_SHARED : MAP_PRIVATE;
145         p = mmap(null, nbytes, PROT_READ | PROT_WRITE, map_f | MAP_ANON, -1, 0);
146         return (p == MAP_FAILED) ? null : p;
147     }
148 
149 
os_mem_unmap(void * base,size_t nbytes)150     int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc
151     {
152         return munmap(base, nbytes);
153     }
154 }
155 else static if (is(typeof(valloc))) // else version (GC_Use_Alloc_Valloc)
156 {
157     enum HaveFork = false;
158 
os_mem_map(size_t nbytes)159     void *os_mem_map(size_t nbytes) nothrow @nogc
160     {
161         return valloc(nbytes);
162     }
163 
164 
os_mem_unmap(void * base,size_t nbytes)165     int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc
166     {
167         free(base);
168         return 0;
169     }
170 }
171 else static if (is(typeof(malloc))) // else version (GC_Use_Alloc_Malloc)
172 {
173     // NOTE: This assumes malloc granularity is at least (void*).sizeof.  If
174     //       (req_size + PAGESIZE) is allocated, and the pointer is rounded up
175     //       to PAGESIZE alignment, there will be space for a void* at the end
176     //       after PAGESIZE bytes used by the GC.
177 
178     enum HaveFork = false;
179 
180     import core.internal.gc.impl.conservative.gc;
181 
182 
183     const size_t PAGE_MASK = PAGESIZE - 1;
184 
185 
os_mem_map(size_t nbytes)186     void *os_mem_map(size_t nbytes) nothrow @nogc
187     {   byte *p, q;
188         p = cast(byte *) malloc(nbytes + PAGESIZE);
189         if (!p)
190             return null;
191         q = p + ((PAGESIZE - ((cast(size_t) p & PAGE_MASK))) & PAGE_MASK);
192         * cast(void**)(q + nbytes) = p;
193         return q;
194     }
195 
196 
os_mem_unmap(void * base,size_t nbytes)197     int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc
198     {
199         free( *cast(void**)( cast(byte*) base + nbytes ) );
200         return 0;
201     }
202 }
203 else
204 {
205     static assert(false, "No supported allocation methods available.");
206 }
207 
208 /**
209    Check for any kind of memory pressure.
210 
211    Params:
212       mapped = the amount of memory mapped by the GC in bytes
213    Returns:
214        true if memory is scarce
215 */
216 // TODO: get virtual mem sizes and current usage from OS
217 // TODO: compare current RSS and avail. physical memory
isLowOnMem(size_t mapped)218 bool isLowOnMem(size_t mapped) nothrow @nogc
219 {
220     version (Windows)
221     {
222         import core.sys.windows.winbase : GlobalMemoryStatusEx, MEMORYSTATUSEX;
223 
224         MEMORYSTATUSEX stat;
225         stat.dwLength = stat.sizeof;
226         const success = GlobalMemoryStatusEx(&stat) != 0;
227         assert(success, "GlobalMemoryStatusEx() failed");
228         if (!success)
229             return false;
230 
231         // dwMemoryLoad is the 'approximate percentage of physical memory that is in use'
232         // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
233         const percentPhysicalRAM = stat.ullTotalPhys / 100;
234         return (stat.dwMemoryLoad >= 95 && mapped > percentPhysicalRAM)
235             || (stat.dwMemoryLoad >= 90 && mapped > 10 * percentPhysicalRAM);
236     }
237     else
238     {
239         enum GB = 2 ^^ 30;
240         version (D_LP64)
241             return false;
242         else version (Darwin)
243         {
244             // 80 % of available 4GB is used for GC (excluding malloc and mmap)
245             enum size_t limit = 4UL * GB * 8 / 10;
246             return mapped > limit;
247         }
248         else
249         {
250             // be conservative and assume 3GB
251             enum size_t limit = 3UL * GB * 8 / 10;
252             return mapped > limit;
253         }
254     }
255 }
256 
257 /**
258    Get the size of available physical memory
259 
260    Returns:
261        size of installed physical RAM
262 */
version(Windows)263 version (Windows)
264 {
265     ulong os_physical_mem() nothrow @nogc
266     {
267         import core.sys.windows.winbase : GlobalMemoryStatus, MEMORYSTATUS;
268         MEMORYSTATUS stat;
269         GlobalMemoryStatus(&stat);
270         return stat.dwTotalPhys; // limited to 4GB for Win32
271     }
272 }
version(Darwin)273 else version (Darwin)
274 {
275     extern (C) int sysctl(const int* name, uint namelen, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) @nogc nothrow;
276     ulong os_physical_mem() nothrow @nogc
277     {
278         enum
279         {
280             CTL_HW = 6,
281             HW_MEMSIZE = 24,
282         }
283         int[2] mib = [ CTL_HW, HW_MEMSIZE ];
284         ulong system_memory_bytes;
285         size_t len = system_memory_bytes.sizeof;
286         if (sysctl(mib.ptr, 2, &system_memory_bytes, &len, null, 0) != 0)
287             return 0;
288         return system_memory_bytes;
289     }
290 }
version(Posix)291 else version (Posix)
292 {
293     ulong os_physical_mem() nothrow @nogc
294     {
295         import core.sys.posix.unistd : sysconf, _SC_PAGESIZE, _SC_PHYS_PAGES;
296         const pageSize = sysconf(_SC_PAGESIZE);
297         const pages = sysconf(_SC_PHYS_PAGES);
298         return pageSize * pages;
299     }
300 }
301