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 */
8module core.internal.gc.os;
9
10
11version (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}
25else 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}
86else
87{
88    import core.stdc.stdlib;
89
90    //version = GC_Use_Alloc_Malloc;
91}
92
93/+
94static if (is(typeof(VirtualAlloc)))
95    version = GC_Use_Alloc_Win32;
96else static if (is(typeof(mmap)))
97    version = GC_Use_Alloc_MMap;
98else static if (is(typeof(valloc)))
99    version = GC_Use_Alloc_Valloc;
100else static if (is(typeof(malloc)))
101    version = GC_Use_Alloc_Malloc;
102else static assert(false, "No supported allocation methods available.");
103+/
104
105static 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     */
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     */
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}
137else 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
150    int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc
151    {
152        return munmap(base, nbytes);
153    }
154}
155else static if (is(typeof(valloc))) // else version (GC_Use_Alloc_Valloc)
156{
157    enum HaveFork = false;
158
159    void *os_mem_map(size_t nbytes) nothrow @nogc
160    {
161        return valloc(nbytes);
162    }
163
164
165    int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc
166    {
167        free(base);
168        return 0;
169    }
170}
171else 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
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
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}
203else
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
218bool 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*/
263version (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}
273else 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}
291else 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