microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/katas-update

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

allocator/mimalloc-sys/mimalloc/src/prim/unix/prim.c

859lines · modecode

1/* ----------------------------------------------------------------------------
2Copyright (c) 2018-2023, Microsoft Research, Daan Leijen
3This is free software; you can redistribute it and/or modify it under the
4terms of the MIT license. A copy of the license can be found in the file
5"LICENSE" at the root of this distribution.
6-----------------------------------------------------------------------------*/
7
8// This file is included in `src/prim/prim.c`
9
10#ifndef _DEFAULT_SOURCE
11#define _DEFAULT_SOURCE // ensure mmap flags and syscall are defined
12#endif
13
14#if defined(__sun)
15// illumos provides new mman.h api when any of these are defined
16// otherwise the old api based on caddr_t which predates the void pointers one.
17// stock solaris provides only the former, chose to atomically to discard those
18// flags only here rather than project wide tough.
19#undef _XOPEN_SOURCE
20#undef _POSIX_C_SOURCE
21#endif
22
23#include "mimalloc.h"
24#include "mimalloc/internal.h"
25#include "mimalloc/atomic.h"
26#include "mimalloc/prim.h"
27
28#include <sys/mman.h> // mmap
29#include <unistd.h> // sysconf
30
31#if defined(__linux__)
32 #include <features.h>
33 #include <fcntl.h>
34 #if defined(__GLIBC__)
35 #include <linux/mman.h> // linux mmap flags
36 #else
37 #include <sys/mman.h>
38 #endif
39#elif defined(__APPLE__)
40 #include <TargetConditionals.h>
41 #if !TARGET_IOS_IPHONE && !TARGET_IOS_SIMULATOR
42 #include <mach/vm_statistics.h>
43 #endif
44#elif defined(__FreeBSD__) || defined(__DragonFly__)
45 #include <sys/param.h>
46 #if __FreeBSD_version >= 1200000
47 #include <sys/cpuset.h>
48 #include <sys/domainset.h>
49 #endif
50 #include <sys/sysctl.h>
51#endif
52
53#if !defined(__HAIKU__) && !defined(__APPLE__) && !defined(__CYGWIN__)
54 #define MI_HAS_SYSCALL_H
55 #include <sys/syscall.h>
56#endif
57
58//------------------------------------------------------------------------------------
59// Use syscalls for some primitives to allow for libraries that override open/read/close etc.
60// and do allocation themselves; using syscalls prevents recursion when mimalloc is
61// still initializing (issue #713)
62//------------------------------------------------------------------------------------
63
64#if defined(MI_HAS_SYSCALL_H) && defined(SYS_open) && defined(SYS_close) && defined(SYS_read) && defined(SYS_access)
65
66static int mi_prim_open(const char* fpath, int open_flags) {
67 return syscall(SYS_open,fpath,open_flags,0);
68}
69static ssize_t mi_prim_read(int fd, void* buf, size_t bufsize) {
70 return syscall(SYS_read,fd,buf,bufsize);
71}
72static int mi_prim_close(int fd) {
73 return syscall(SYS_close,fd);
74}
75static int mi_prim_access(const char *fpath, int mode) {
76 return syscall(SYS_access,fpath,mode);
77}
78
79#elif !defined(__APPLE__) // avoid unused warnings
80
81static int mi_prim_open(const char* fpath, int open_flags) {
82 return open(fpath,open_flags);
83}
84static ssize_t mi_prim_read(int fd, void* buf, size_t bufsize) {
85 return read(fd,buf,bufsize);
86}
87static int mi_prim_close(int fd) {
88 return close(fd);
89}
90static int mi_prim_access(const char *fpath, int mode) {
91 return access(fpath,mode);
92}
93
94#endif
95
96
97
98//---------------------------------------------
99// init
100//---------------------------------------------
101
102static bool unix_detect_overcommit(void) {
103 bool os_overcommit = true;
104#if defined(__linux__)
105 int fd = mi_prim_open("/proc/sys/vm/overcommit_memory", O_RDONLY);
106 if (fd >= 0) {
107 char buf[32];
108 ssize_t nread = mi_prim_read(fd, &buf, sizeof(buf));
109 mi_prim_close(fd);
110 // <https://www.kernel.org/doc/Documentation/vm/overcommit-accounting>
111 // 0: heuristic overcommit, 1: always overcommit, 2: never overcommit (ignore NORESERVE)
112 if (nread >= 1) {
113 os_overcommit = (buf[0] == '0' || buf[0] == '1');
114 }
115 }
116#elif defined(__FreeBSD__)
117 int val = 0;
118 size_t olen = sizeof(val);
119 if (sysctlbyname("vm.overcommit", &val, &olen, NULL, 0) == 0) {
120 os_overcommit = (val != 0);
121 }
122#else
123 // default: overcommit is true
124#endif
125 return os_overcommit;
126}
127
128void _mi_prim_mem_init( mi_os_mem_config_t* config ) {
129 long psize = sysconf(_SC_PAGESIZE);
130 if (psize > 0) {
131 config->page_size = (size_t)psize;
132 config->alloc_granularity = (size_t)psize;
133 }
134 config->large_page_size = 2*MI_MiB; // TODO: can we query the OS for this?
135 config->has_overcommit = unix_detect_overcommit();
136 config->must_free_whole = false; // mmap can free in parts
137 config->has_virtual_reserve = true; // todo: check if this true for NetBSD? (for anonymous mmap with PROT_NONE)
138}
139
140
141//---------------------------------------------
142// free
143//---------------------------------------------
144
145int _mi_prim_free(void* addr, size_t size ) {
146 bool err = (munmap(addr, size) == -1);
147 return (err ? errno : 0);
148}
149
150
151//---------------------------------------------
152// mmap
153//---------------------------------------------
154
155static int unix_madvise(void* addr, size_t size, int advice) {
156 #if defined(__sun)
157 return madvise((caddr_t)addr, size, advice); // Solaris needs cast (issue #520)
158 #else
159 return madvise(addr, size, advice);
160 #endif
161}
162
163static void* unix_mmap_prim(void* addr, size_t size, size_t try_alignment, int protect_flags, int flags, int fd) {
164 MI_UNUSED(try_alignment);
165 void* p = NULL;
166 #if defined(MAP_ALIGNED) // BSD
167 if (addr == NULL && try_alignment > 1 && (try_alignment % _mi_os_page_size()) == 0) {
168 size_t n = mi_bsr(try_alignment);
169 if (((size_t)1 << n) == try_alignment && n >= 12 && n <= 30) { // alignment is a power of 2 and 4096 <= alignment <= 1GiB
170 p = mmap(addr, size, protect_flags, flags | MAP_ALIGNED(n), fd, 0);
171 if (p==MAP_FAILED || !_mi_is_aligned(p,try_alignment)) {
172 int err = errno;
173 _mi_warning_message("unable to directly request aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, addr);
174 }
175 if (p!=MAP_FAILED) return p;
176 // fall back to regular mmap
177 }
178 }
179 #elif defined(MAP_ALIGN) // Solaris
180 if (addr == NULL && try_alignment > 1 && (try_alignment % _mi_os_page_size()) == 0) {
181 p = mmap((void*)try_alignment, size, protect_flags, flags | MAP_ALIGN, fd, 0); // addr parameter is the required alignment
182 if (p!=MAP_FAILED) return p;
183 // fall back to regular mmap
184 }
185 #endif
186 #if (MI_INTPTR_SIZE >= 8) && !defined(MAP_ALIGNED)
187 // on 64-bit systems, use the virtual address area after 2TiB for 4MiB aligned allocations
188 if (addr == NULL) {
189 void* hint = _mi_os_get_aligned_hint(try_alignment, size);
190 if (hint != NULL) {
191 p = mmap(hint, size, protect_flags, flags, fd, 0);
192 if (p==MAP_FAILED || !_mi_is_aligned(p,try_alignment)) {
193 #if MI_TRACK_ENABLED // asan sometimes does not instrument errno correctly?
194 int err = 0;
195 #else
196 int err = errno;
197 #endif
198 _mi_warning_message("unable to directly request hinted aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, hint);
199 }
200 if (p!=MAP_FAILED) return p;
201 // fall back to regular mmap
202 }
203 }
204 #endif
205 // regular mmap
206 p = mmap(addr, size, protect_flags, flags, fd, 0);
207 if (p!=MAP_FAILED) return p;
208 // failed to allocate
209 return NULL;
210}
211
212static int unix_mmap_fd(void) {
213 #if defined(VM_MAKE_TAG)
214 // macOS: tracking anonymous page with a specific ID. (All up to 98 are taken officially but LLVM sanitizers had taken 99)
215 int os_tag = (int)mi_option_get(mi_option_os_tag);
216 if (os_tag < 100 || os_tag > 255) { os_tag = 100; }
217 return VM_MAKE_TAG(os_tag);
218 #else
219 return -1;
220 #endif
221}
222
223static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protect_flags, bool large_only, bool allow_large, bool* is_large) {
224 #if !defined(MAP_ANONYMOUS)
225 #define MAP_ANONYMOUS MAP_ANON
226 #endif
227 #if !defined(MAP_NORESERVE)
228 #define MAP_NORESERVE 0
229 #endif
230 void* p = NULL;
231 const int fd = unix_mmap_fd();
232 int flags = MAP_PRIVATE | MAP_ANONYMOUS;
233 if (_mi_os_has_overcommit()) {
234 flags |= MAP_NORESERVE;
235 }
236 #if defined(PROT_MAX)
237 protect_flags |= PROT_MAX(PROT_READ | PROT_WRITE); // BSD
238 #endif
239 // huge page allocation
240 if ((large_only || _mi_os_use_large_page(size, try_alignment)) && allow_large) {
241 static _Atomic(size_t) large_page_try_ok; // = 0;
242 size_t try_ok = mi_atomic_load_acquire(&large_page_try_ok);
243 if (!large_only && try_ok > 0) {
244 // If the OS is not configured for large OS pages, or the user does not have
245 // enough permission, the `mmap` will always fail (but it might also fail for other reasons).
246 // Therefore, once a large page allocation failed, we don't try again for `large_page_try_ok` times
247 // to avoid too many failing calls to mmap.
248 mi_atomic_cas_strong_acq_rel(&large_page_try_ok, &try_ok, try_ok - 1);
249 }
250 else {
251 int lflags = flags & ~MAP_NORESERVE; // using NORESERVE on huge pages seems to fail on Linux
252 int lfd = fd;
253 #ifdef MAP_ALIGNED_SUPER
254 lflags |= MAP_ALIGNED_SUPER;
255 #endif
256 #ifdef MAP_HUGETLB
257 lflags |= MAP_HUGETLB;
258 #endif
259 #ifdef MAP_HUGE_1GB
260 static bool mi_huge_pages_available = true;
261 if ((size % MI_GiB) == 0 && mi_huge_pages_available) {
262 lflags |= MAP_HUGE_1GB;
263 }
264 else
265 #endif
266 {
267 #ifdef MAP_HUGE_2MB
268 lflags |= MAP_HUGE_2MB;
269 #endif
270 }
271 #ifdef VM_FLAGS_SUPERPAGE_SIZE_2MB
272 lfd |= VM_FLAGS_SUPERPAGE_SIZE_2MB;
273 #endif
274 if (large_only || lflags != flags) {
275 // try large OS page allocation
276 *is_large = true;
277 p = unix_mmap_prim(addr, size, try_alignment, protect_flags, lflags, lfd);
278 #ifdef MAP_HUGE_1GB
279 if (p == NULL && (lflags & MAP_HUGE_1GB) != 0) {
280 mi_huge_pages_available = false; // don't try huge 1GiB pages again
281 _mi_warning_message("unable to allocate huge (1GiB) page, trying large (2MiB) pages instead (errno: %i)\n", errno);
282 lflags = ((lflags & ~MAP_HUGE_1GB) | MAP_HUGE_2MB);
283 p = unix_mmap_prim(addr, size, try_alignment, protect_flags, lflags, lfd);
284 }
285 #endif
286 if (large_only) return p;
287 if (p == NULL) {
288 mi_atomic_store_release(&large_page_try_ok, (size_t)8); // on error, don't try again for the next N allocations
289 }
290 }
291 }
292 }
293 // regular allocation
294 if (p == NULL) {
295 *is_large = false;
296 p = unix_mmap_prim(addr, size, try_alignment, protect_flags, flags, fd);
297 if (p != NULL) {
298 #if defined(MADV_HUGEPAGE)
299 // Many Linux systems don't allow MAP_HUGETLB but they support instead
300 // transparent huge pages (THP). Generally, it is not required to call `madvise` with MADV_HUGE
301 // though since properly aligned allocations will already use large pages if available
302 // in that case -- in particular for our large regions (in `memory.c`).
303 // However, some systems only allow THP if called with explicit `madvise`, so
304 // when large OS pages are enabled for mimalloc, we call `madvise` anyways.
305 if (allow_large && _mi_os_use_large_page(size, try_alignment)) {
306 if (unix_madvise(p, size, MADV_HUGEPAGE) == 0) {
307 *is_large = true; // possibly
308 };
309 }
310 #elif defined(__sun)
311 if (allow_large && _mi_os_use_large_page(size, try_alignment)) {
312 struct memcntl_mha cmd = {0};
313 cmd.mha_pagesize = large_os_page_size;
314 cmd.mha_cmd = MHA_MAPSIZE_VA;
315 if (memcntl((caddr_t)p, size, MC_HAT_ADVISE, (caddr_t)&cmd, 0, 0) == 0) {
316 *is_large = true;
317 }
318 }
319 #endif
320 }
321 }
322 return p;
323}
324
325// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.
326int _mi_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {
327 mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);
328 mi_assert_internal(commit || !allow_large);
329 mi_assert_internal(try_alignment > 0);
330
331 *is_zero = true;
332 int protect_flags = (commit ? (PROT_WRITE | PROT_READ) : PROT_NONE);
333 *addr = unix_mmap(NULL, size, try_alignment, protect_flags, false, allow_large, is_large);
334 return (*addr != NULL ? 0 : errno);
335}
336
337
338//---------------------------------------------
339// Commit/Reset
340//---------------------------------------------
341
342static void unix_mprotect_hint(int err) {
343 #if defined(__linux__) && (MI_SECURE>=2) // guard page around every mimalloc page
344 if (err == ENOMEM) {
345 _mi_warning_message("The next warning may be caused by a low memory map limit.\n"
346 " On Linux this is controlled by the vm.max_map_count -- maybe increase it?\n"
347 " For example: sudo sysctl -w vm.max_map_count=262144\n");
348 }
349 #else
350 MI_UNUSED(err);
351 #endif
352}
353
354int _mi_prim_commit(void* start, size_t size, bool* is_zero) {
355 // commit: ensure we can access the area
356 // note: we may think that *is_zero can be true since the memory
357 // was either from mmap PROT_NONE, or from decommit MADV_DONTNEED, but
358 // we sometimes call commit on a range with still partially committed
359 // memory and `mprotect` does not zero the range.
360 *is_zero = false;
361 int err = mprotect(start, size, (PROT_READ | PROT_WRITE));
362 if (err != 0) {
363 err = errno;
364 unix_mprotect_hint(err);
365 }
366 return err;
367}
368
369int _mi_prim_decommit(void* start, size_t size, bool* needs_recommit) {
370 int err = 0;
371 // decommit: use MADV_DONTNEED as it decreases rss immediately (unlike MADV_FREE)
372 err = unix_madvise(start, size, MADV_DONTNEED);
373 #if !MI_DEBUG && !MI_SECURE
374 *needs_recommit = false;
375 #else
376 *needs_recommit = true;
377 mprotect(start, size, PROT_NONE);
378 #endif
379 /*
380 // decommit: use mmap with MAP_FIXED and PROT_NONE to discard the existing memory (and reduce rss)
381 *needs_recommit = true;
382 const int fd = unix_mmap_fd();
383 void* p = mmap(start, size, PROT_NONE, (MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE), fd, 0);
384 if (p != start) { err = errno; }
385 */
386 return err;
387}
388
389int _mi_prim_reset(void* start, size_t size) {
390 // We try to use `MADV_FREE` as that is the fastest. A drawback though is that it
391 // will not reduce the `rss` stats in tools like `top` even though the memory is available
392 // to other processes. With the default `MIMALLOC_PURGE_DECOMMITS=1` we ensure that by
393 // default `MADV_DONTNEED` is used though.
394 #if defined(MADV_FREE)
395 static _Atomic(size_t) advice = MI_ATOMIC_VAR_INIT(MADV_FREE);
396 int oadvice = (int)mi_atomic_load_relaxed(&advice);
397 int err;
398 while ((err = unix_madvise(start, size, oadvice)) != 0 && errno == EAGAIN) { errno = 0; };
399 if (err != 0 && errno == EINVAL && oadvice == MADV_FREE) {
400 // if MADV_FREE is not supported, fall back to MADV_DONTNEED from now on
401 mi_atomic_store_release(&advice, (size_t)MADV_DONTNEED);
402 err = unix_madvise(start, size, MADV_DONTNEED);
403 }
404 #else
405 int err = unix_madvise(start, size, MADV_DONTNEED);
406 #endif
407 return err;
408}
409
410int _mi_prim_protect(void* start, size_t size, bool protect) {
411 int err = mprotect(start, size, protect ? PROT_NONE : (PROT_READ | PROT_WRITE));
412 if (err != 0) { err = errno; }
413 unix_mprotect_hint(err);
414 return err;
415}
416
417
418
419//---------------------------------------------
420// Huge page allocation
421//---------------------------------------------
422
423#if (MI_INTPTR_SIZE >= 8) && !defined(__HAIKU__) && !defined(__CYGWIN__)
424
425#ifndef MPOL_PREFERRED
426#define MPOL_PREFERRED 1
427#endif
428
429#if defined(MI_HAS_SYSCALL_H) && defined(SYS_mbind)
430static long mi_prim_mbind(void* start, unsigned long len, unsigned long mode, const unsigned long* nmask, unsigned long maxnode, unsigned flags) {
431 return syscall(SYS_mbind, start, len, mode, nmask, maxnode, flags);
432}
433#else
434static long mi_prim_mbind(void* start, unsigned long len, unsigned long mode, const unsigned long* nmask, unsigned long maxnode, unsigned flags) {
435 MI_UNUSED(start); MI_UNUSED(len); MI_UNUSED(mode); MI_UNUSED(nmask); MI_UNUSED(maxnode); MI_UNUSED(flags);
436 return 0;
437}
438#endif
439
440int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) {
441 bool is_large = true;
442 *is_zero = true;
443 *addr = unix_mmap(hint_addr, size, MI_SEGMENT_SIZE, PROT_READ | PROT_WRITE, true, true, &is_large);
444 if (*addr != NULL && numa_node >= 0 && numa_node < 8*MI_INTPTR_SIZE) { // at most 64 nodes
445 unsigned long numa_mask = (1UL << numa_node);
446 // TODO: does `mbind` work correctly for huge OS pages? should we
447 // use `set_mempolicy` before calling mmap instead?
448 // see: <https://lkml.org/lkml/2017/2/9/875>
449 long err = mi_prim_mbind(*addr, size, MPOL_PREFERRED, &numa_mask, 8*MI_INTPTR_SIZE, 0);
450 if (err != 0) {
451 err = errno;
452 _mi_warning_message("failed to bind huge (1GiB) pages to numa node %d (error: %d (0x%x))\n", numa_node, err, err);
453 }
454 }
455 return (*addr != NULL ? 0 : errno);
456}
457
458#else
459
460int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) {
461 MI_UNUSED(hint_addr); MI_UNUSED(size); MI_UNUSED(numa_node);
462 *is_zero = false;
463 *addr = NULL;
464 return ENOMEM;
465}
466
467#endif
468
469//---------------------------------------------
470// NUMA nodes
471//---------------------------------------------
472
473#if defined(__linux__)
474
475#include <stdio.h> // snprintf
476
477size_t _mi_prim_numa_node(void) {
478 #if defined(MI_HAS_SYSCALL_H) && defined(SYS_getcpu)
479 unsigned long node = 0;
480 unsigned long ncpu = 0;
481 long err = syscall(SYS_getcpu, &ncpu, &node, NULL);
482 if (err != 0) return 0;
483 return node;
484 #else
485 return 0;
486 #endif
487}
488
489size_t _mi_prim_numa_node_count(void) {
490 char buf[128];
491 unsigned node = 0;
492 for(node = 0; node < 256; node++) {
493 // enumerate node entries -- todo: it there a more efficient way to do this? (but ensure there is no allocation)
494 snprintf(buf, 127, "/sys/devices/system/node/node%u", node + 1);
495 if (mi_prim_access(buf,R_OK) != 0) break;
496 }
497 return (node+1);
498}
499
500#elif defined(__FreeBSD__) && __FreeBSD_version >= 1200000
501
502size_t _mi_prim_numa_node(void) {
503 domainset_t dom;
504 size_t node;
505 int policy;
506 if (cpuset_getdomain(CPU_LEVEL_CPUSET, CPU_WHICH_PID, -1, sizeof(dom), &dom, &policy) == -1) return 0ul;
507 for (node = 0; node < MAXMEMDOM; node++) {
508 if (DOMAINSET_ISSET(node, &dom)) return node;
509 }
510 return 0ul;
511}
512
513size_t _mi_prim_numa_node_count(void) {
514 size_t ndomains = 0;
515 size_t len = sizeof(ndomains);
516 if (sysctlbyname("vm.ndomains", &ndomains, &len, NULL, 0) == -1) return 0ul;
517 return ndomains;
518}
519
520#elif defined(__DragonFly__)
521
522size_t _mi_prim_numa_node(void) {
523 // TODO: DragonFly does not seem to provide any userland means to get this information.
524 return 0ul;
525}
526
527size_t _mi_prim_numa_node_count(void) {
528 size_t ncpus = 0, nvirtcoresperphys = 0;
529 size_t len = sizeof(size_t);
530 if (sysctlbyname("hw.ncpu", &ncpus, &len, NULL, 0) == -1) return 0ul;
531 if (sysctlbyname("hw.cpu_topology_ht_ids", &nvirtcoresperphys, &len, NULL, 0) == -1) return 0ul;
532 return nvirtcoresperphys * ncpus;
533}
534
535#else
536
537size_t _mi_prim_numa_node(void) {
538 return 0;
539}
540
541size_t _mi_prim_numa_node_count(void) {
542 return 1;
543}
544
545#endif
546
547// ----------------------------------------------------------------
548// Clock
549// ----------------------------------------------------------------
550
551#include <time.h>
552
553#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
554
555mi_msecs_t _mi_prim_clock_now(void) {
556 struct timespec t;
557 #ifdef CLOCK_MONOTONIC
558 clock_gettime(CLOCK_MONOTONIC, &t);
559 #else
560 clock_gettime(CLOCK_REALTIME, &t);
561 #endif
562 return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000);
563}
564
565#else
566
567// low resolution timer
568mi_msecs_t _mi_prim_clock_now(void) {
569 #if !defined(CLOCKS_PER_SEC) || (CLOCKS_PER_SEC == 1000) || (CLOCKS_PER_SEC == 0)
570 return (mi_msecs_t)clock();
571 #elif (CLOCKS_PER_SEC < 1000)
572 return (mi_msecs_t)clock() * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
573 #else
574 return (mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
575 #endif
576}
577
578#endif
579
580
581
582
583//----------------------------------------------------------------
584// Process info
585//----------------------------------------------------------------
586
587#if defined(__unix__) || defined(__unix) || defined(unix) || defined(__APPLE__) || defined(__HAIKU__)
588#include <stdio.h>
589#include <unistd.h>
590#include <sys/resource.h>
591
592#if defined(__APPLE__)
593#include <mach/mach.h>
594#endif
595
596#if defined(__HAIKU__)
597#include <kernel/OS.h>
598#endif
599
600static mi_msecs_t timeval_secs(const struct timeval* tv) {
601 return ((mi_msecs_t)tv->tv_sec * 1000L) + ((mi_msecs_t)tv->tv_usec / 1000L);
602}
603
604void _mi_prim_process_info(mi_process_info_t* pinfo)
605{
606 struct rusage rusage;
607 getrusage(RUSAGE_SELF, &rusage);
608 pinfo->utime = timeval_secs(&rusage.ru_utime);
609 pinfo->stime = timeval_secs(&rusage.ru_stime);
610#if !defined(__HAIKU__)
611 pinfo->page_faults = rusage.ru_majflt;
612#endif
613#if defined(__HAIKU__)
614 // Haiku does not have (yet?) a way to
615 // get these stats per process
616 thread_info tid;
617 area_info mem;
618 ssize_t c;
619 get_thread_info(find_thread(0), &tid);
620 while (get_next_area_info(tid.team, &c, &mem) == B_OK) {
621 pinfo->peak_rss += mem.ram_size;
622 }
623 pinfo->page_faults = 0;
624#elif defined(__APPLE__)
625 pinfo->peak_rss = rusage.ru_maxrss; // macos reports in bytes
626 #ifdef MACH_TASK_BASIC_INFO
627 struct mach_task_basic_info info;
628 mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
629 if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
630 pinfo->current_rss = (size_t)info.resident_size;
631 }
632 #else
633 struct task_basic_info info;
634 mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
635 if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
636 pinfo->current_rss = (size_t)info.resident_size;
637 }
638 #endif
639#else
640 pinfo->peak_rss = rusage.ru_maxrss * 1024; // Linux/BSD report in KiB
641#endif
642 // use defaults for commit
643}
644
645#else
646
647#ifndef __wasi__
648// WebAssembly instances are not processes
649#pragma message("define a way to get process info")
650#endif
651
652void _mi_prim_process_info(mi_process_info_t* pinfo)
653{
654 // use defaults
655 MI_UNUSED(pinfo);
656}
657
658#endif
659
660
661//----------------------------------------------------------------
662// Output
663//----------------------------------------------------------------
664
665void _mi_prim_out_stderr( const char* msg ) {
666 fputs(msg,stderr);
667}
668
669
670//----------------------------------------------------------------
671// Environment
672//----------------------------------------------------------------
673
674#if !defined(MI_USE_ENVIRON) || (MI_USE_ENVIRON!=0)
675// On Posix systemsr use `environ` to access environment variables
676// even before the C runtime is initialized.
677#if defined(__APPLE__) && defined(__has_include) && __has_include(<crt_externs.h>)
678#include <crt_externs.h>
679static char** mi_get_environ(void) {
680 return (*_NSGetEnviron());
681}
682#else
683extern char** environ;
684static char** mi_get_environ(void) {
685 return environ;
686}
687#endif
688bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
689 if (name==NULL) return false;
690 const size_t len = _mi_strlen(name);
691 if (len == 0) return false;
692 char** env = mi_get_environ();
693 if (env == NULL) return false;
694 // compare up to 10000 entries
695 for (int i = 0; i < 10000 && env[i] != NULL; i++) {
696 const char* s = env[i];
697 if (_mi_strnicmp(name, s, len) == 0 && s[len] == '=') { // case insensitive
698 // found it
699 _mi_strlcpy(result, s + len + 1, result_size);
700 return true;
701 }
702 }
703 return false;
704}
705#else
706// fallback: use standard C `getenv` but this cannot be used while initializing the C runtime
707bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
708 // cannot call getenv() when still initializing the C runtime.
709 if (_mi_preloading()) return false;
710 const char* s = getenv(name);
711 if (s == NULL) {
712 // we check the upper case name too.
713 char buf[64+1];
714 size_t len = _mi_strnlen(name,sizeof(buf)-1);
715 for (size_t i = 0; i < len; i++) {
716 buf[i] = _mi_toupper(name[i]);
717 }
718 buf[len] = 0;
719 s = getenv(buf);
720 }
721 if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return false;
722 _mi_strlcpy(result, s, result_size);
723 return true;
724}
725#endif // !MI_USE_ENVIRON
726
727
728//----------------------------------------------------------------
729// Random
730//----------------------------------------------------------------
731
732#if defined(__APPLE__)
733
734#include <AvailabilityMacros.h>
735#if defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10
736#include <CommonCrypto/CommonCryptoError.h>
737#include <CommonCrypto/CommonRandom.h>
738#endif
739bool _mi_prim_random_buf(void* buf, size_t buf_len) {
740 #if defined(MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
741 // We prefere CCRandomGenerateBytes as it returns an error code while arc4random_buf
742 // may fail silently on macOS. See PR #390, and <https://opensource.apple.com/source/Libc/Libc-1439.40.11/gen/FreeBSD/arc4random.c.auto.html>
743 return (CCRandomGenerateBytes(buf, buf_len) == kCCSuccess);
744 #else
745 // fall back on older macOS
746 arc4random_buf(buf, buf_len);
747 return true;
748 #endif
749}
750
751#elif defined(__ANDROID__) || defined(__DragonFly__) || \
752 defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
753 defined(__sun)
754
755#include <stdlib.h>
756bool _mi_prim_random_buf(void* buf, size_t buf_len) {
757 arc4random_buf(buf, buf_len);
758 return true;
759}
760
761#elif defined(__linux__) || defined(__HAIKU__)
762
763#include <sys/types.h>
764#include <sys/stat.h>
765#include <fcntl.h>
766#include <errno.h>
767
768bool _mi_prim_random_buf(void* buf, size_t buf_len) {
769 // Modern Linux provides `getrandom` but different distributions either use `sys/random.h` or `linux/random.h`
770 // and for the latter the actual `getrandom` call is not always defined.
771 // (see <https://stackoverflow.com/questions/45237324/why-doesnt-getrandom-compile>)
772 // We therefore use a syscall directly and fall back dynamically to /dev/urandom when needed.
773 #if defined(MI_HAS_SYSCALL_H) && defined(SYS_getrandom)
774 #ifndef GRND_NONBLOCK
775 #define GRND_NONBLOCK (1)
776 #endif
777 static _Atomic(uintptr_t) no_getrandom; // = 0
778 if (mi_atomic_load_acquire(&no_getrandom)==0) {
779 ssize_t ret = syscall(SYS_getrandom, buf, buf_len, GRND_NONBLOCK);
780 if (ret >= 0) return (buf_len == (size_t)ret);
781 if (errno != ENOSYS) return false;
782 mi_atomic_store_release(&no_getrandom, (uintptr_t)1); // don't call again, and fall back to /dev/urandom
783 }
784 #endif
785 int flags = O_RDONLY;
786 #if defined(O_CLOEXEC)
787 flags |= O_CLOEXEC;
788 #endif
789 int fd = mi_prim_open("/dev/urandom", flags);
790 if (fd < 0) return false;
791 size_t count = 0;
792 while(count < buf_len) {
793 ssize_t ret = mi_prim_read(fd, (char*)buf + count, buf_len - count);
794 if (ret<=0) {
795 if (errno!=EAGAIN && errno!=EINTR) break;
796 }
797 else {
798 count += ret;
799 }
800 }
801 mi_prim_close(fd);
802 return (count==buf_len);
803}
804
805#else
806
807bool _mi_prim_random_buf(void* buf, size_t buf_len) {
808 return false;
809}
810
811#endif
812
813
814//----------------------------------------------------------------
815// Thread init/done
816//----------------------------------------------------------------
817
818#if defined(MI_USE_PTHREADS)
819
820// use pthread local storage keys to detect thread ending
821// (and used with MI_TLS_PTHREADS for the default heap)
822pthread_key_t _mi_heap_default_key = (pthread_key_t)(-1);
823
824static void mi_pthread_done(void* value) {
825 if (value!=NULL) {
826 _mi_thread_done((mi_heap_t*)value);
827 }
828}
829
830void _mi_prim_thread_init_auto_done(void) {
831 mi_assert_internal(_mi_heap_default_key == (pthread_key_t)(-1));
832 pthread_key_create(&_mi_heap_default_key, &mi_pthread_done);
833}
834
835void _mi_prim_thread_done_auto_done(void) {
836 // nothing to do
837}
838
839void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {
840 if (_mi_heap_default_key != (pthread_key_t)(-1)) { // can happen during recursive invocation on freeBSD
841 pthread_setspecific(_mi_heap_default_key, heap);
842 }
843}
844
845#else
846
847void _mi_prim_thread_init_auto_done(void) {
848 // nothing
849}
850
851void _mi_prim_thread_done_auto_done(void) {
852 // nothing
853}
854
855void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {
856 MI_UNUSED(heap);
857}
858
859#endif
860