1 /*
2  * Geario - A cross-platform abstraction library with asynchronous I/O.
3  *
4  * Copyright (C) 2021-2022 Kerisy.com
5  *
6  * Website: https://www.kerisy.com
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module geario.system.Memory;
13 
14 import core.atomic;
15 import core.memory;
16 
17 import std.traits;
18 
19 version (OSX) {
20     version = useSysctlbyname;
21 }
22 else version (FreeBSD) {
23     version = useSysctlbyname;
24 }
25 else version (DragonFlyBSD) {
26     version = useSysctlbyname;
27 }
28 else version (NetBSD) {
29     version = useSysctlbyname;
30 }
31 
32 version(useSysctlbyname)
33     private extern(C) int sysctlbyname(
34         const char *, void *, size_t *, void *, size_t
35     ) @nogc nothrow;
36 
37 /*
38 (For now public undocumented with reserved name.)
39 
40 A lazily initialized global constant. The underlying value is a shared global
41 statically initialized to `outOfBandValue` which must not be a legit value of
42 the constant. Upon the first call the situation is detected and the global is
43 initialized by calling `initializer`. The initializer is assumed to be pure
44 (even if not marked as such), i.e. return the same value upon repeated calls.
45 For that reason, no special precautions are taken so `initializer` may be called
46 more than one time leading to benign races on the cached value.
47 
48 In the quiescent state the cost of the function is an atomic load from a global.
49 
50 Params:
51     T = The type of the pseudo-constant (may be qualified)
52     outOfBandValue = A value that cannot be valid, it is used for initialization
53     initializer = The function performing initialization; must be `nothrow`
54 
55 Returns:
56     The lazily initialized value
57 */
58 @property pure T __lazilyInitializedConstant(T, alias outOfBandValue, alias initializer)()
59         if (is(Unqual!T : T) && is(typeof(initializer()) : T) && is(typeof(outOfBandValue) : T)) {
60     static T Impl() nothrow {
61         // Thread-local cache
62         static Unqual!T tls = outOfBandValue;
63         auto local = tls;
64         // Shortest path, no atomic operations
65         if (local != outOfBandValue)
66             return local;
67         // Process-level cache
68         static shared Unqual!T result = outOfBandValue;
69         // Initialize both process-level cache and tls
70         local = atomicLoad(result);
71         if (local == outOfBandValue) {
72             local = initializer();
73             atomicStore(result, local);
74         }
75         tls = local;
76         return local;
77     }
78 
79     // import std.traits : SetFunctionAttributes;
80     alias Fun = SetFunctionAttributes!(typeof(&Impl), "D",
81             functionAttributes!(typeof(&Impl)) | FunctionAttribute.pure_);
82     auto purified = (() @trusted => cast(Fun)&Impl)();
83     return purified();
84 }
85 
86 /**
87 The total number of CPU cores available on the current machine, as reported by
88 the operating system.
89 */
90 alias totalCPUs = __lazilyInitializedConstant!(immutable(uint), uint.max, TotalCPUsImpl);
91 
92 uint TotalCPUsImpl() @nogc nothrow @trusted {
93     version (Windows) {
94         // BUGS:  Only works on Windows 2000 and above.
95         import core.sys.windows.windows : SYSTEM_INFO, GetSystemInfo;
96         import std.algorithm.comparison : max;
97 
98         SYSTEM_INFO si;
99         GetSystemInfo(&si);
100         return max(1, cast(uint) si.dwNumberOfProcessors);
101     }
102     else version (linux) {
103         import core.sys.posix.unistd : _SC_NPROCESSORS_ONLN, sysconf;
104 
105         return cast(uint) sysconf(_SC_NPROCESSORS_ONLN);
106     }
107     else version (Solaris) {
108         import core.sys.posix.unistd : _SC_NPROCESSORS_ONLN, sysconf;
109 
110         return cast(uint) sysconf(_SC_NPROCESSORS_ONLN);
111     }
112     else version (useSysctlbyname) {
113         version (OSX) {
114             auto nameStr = "machdep.cpu.core_count\0".ptr;
115         }
116         else version (FreeBSD) {
117             auto nameStr = "hw.ncpu\0".ptr;
118         }
119         else version (DragonFlyBSD) {
120             auto nameStr = "hw.ncpu\0".ptr;
121         }
122         else version (NetBSD) {
123             auto nameStr = "hw.ncpu\0".ptr;
124         }
125 
126         uint result;
127         size_t len = result.sizeof;
128         sysctlbyname(nameStr, &result, &len, null, 0);
129         return result;
130     }
131     else {
132         static assert(0, "Don't know how to get N CPUs on this OS.");
133     }
134 }
135 
136 /**
137 */
138 size_t PageSize() @safe pure nothrow @nogc {
139     return _pageSize;
140 }
141 
142 static immutable size_t _pageSize;
143 
144 shared static this() {
145     version (Windows) {
146         import core.sys.windows.winbase;
147 
148         SYSTEM_INFO Info;
149         GetSystemInfo(&Info);
150 
151         _pageSize = Info.dwPageSize;
152         assert(_pageSize < int.max);
153     }
154     else version (Posix) {
155         import core.sys.posix.unistd;
156 
157         _pageSize = cast(size_t) sysconf(_SC_PAGESIZE);
158     }
159     else {
160         static assert(0, "unimplemented");
161     }
162 }