Add cross-platform NUMA library
[blender.git] / intern / numaapi / source / numaapi_win32.c
1 // Copyright (c) 2016, libnumaapi authors
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to
5 // deal in the Software without restriction, including without limitation the
6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 // sell copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
9 //
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
12 //
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 // IN THE SOFTWARE.
20 //
21 // Author: Sergey Sharybin (sergey.vfx@gmail.com)
22
23 #include "build_config.h"
24
25 #if OS_WIN
26
27 #include "numaapi.h"
28
29 #ifndef NOGDI
30 #  define NOGDI
31 #endif
32 #ifndef NOMINMAX
33 #  define NOMINMAX
34 #endif
35 #ifndef WIN32_LEAN_AND_MEAN
36 #  define WIN32_LEAN_AND_MEAN
37 #endif
38 #ifndef NOCOMM
39 #  define NOCOMM
40 #endif
41
42 #include <stdlib.h>
43 #include <stdint.h>
44 #include <windows.h>
45
46 #if ARCH_CPU_64_BITS
47 #  include <VersionHelpers.h>
48 #endif
49
50 #include <stdio.h>
51
52 ////////////////////////////////////////////////////////////////////////////////
53 // Initialization.
54
55 // Kernel library, from where the symbols come.
56 static HMODULE kernel_lib;
57
58 // Types of all symbols which are read from the library.
59
60 // NUMA function types.
61 typedef BOOL t_GetNumaHighestNodeNumber(PULONG highest_node_number);
62 typedef BOOL t_GetNumaNodeProcessorMask(UCHAR node, ULONGLONG* processor_mask);
63 typedef BOOL t_GetNumaNodeProcessorMaskEx(USHORT node,
64                                           GROUP_AFFINITY* processor_mask);
65 typedef BOOL t_GetNumaProcessorNode(UCHAR processor, UCHAR* node_number);
66 typedef void* t_VirtualAllocExNuma(HANDLE process_handle,
67                                    LPVOID address,
68                                    SIZE_T size,
69                                    DWORD  allocation_type,
70                                    DWORD  protect,
71                                    DWORD  preferred);
72 typedef BOOL t_VirtualFree(void* address, SIZE_T size, DWORD free_type);
73 // Threading function types.
74 typedef BOOL t_SetProcessAffinityMask(HANDLE process_handle,
75                                       DWORD_PTR process_affinity_mask);
76 typedef BOOL t_SetThreadGroupAffinity(HANDLE thread_handle,
77                                       const GROUP_AFFINITY* GroupAffinity,
78                                       GROUP_AFFINITY* PreviousGroupAffinity);
79 typedef DWORD t_GetCurrentProcessorNumber(void);
80
81 // NUMA symbols.
82 static t_GetNumaHighestNodeNumber* _GetNumaHighestNodeNumber;
83 static t_GetNumaNodeProcessorMask* _GetNumaNodeProcessorMask;
84 static t_GetNumaNodeProcessorMaskEx* _GetNumaNodeProcessorMaskEx;
85 static t_GetNumaProcessorNode* _GetNumaProcessorNode;
86 static t_VirtualAllocExNuma* _VirtualAllocExNuma;
87 static t_VirtualFree* _VirtualFree;
88 // Threading symbols.
89 static t_SetProcessAffinityMask* _SetProcessAffinityMask;
90 static t_SetThreadGroupAffinity* _SetThreadGroupAffinity;
91 static t_GetCurrentProcessorNumber* _GetCurrentProcessorNumber;
92
93 static void numaExit(void) {
94   // TODO(sergey): Consider closing library here.
95 }
96
97 static NUMAAPI_Result loadNumaSymbols(void) {
98   // Prevent multiple initializations.
99   static bool initialized = false;
100   static NUMAAPI_Result result = NUMAAPI_NOT_AVAILABLE;
101   if (initialized) {
102     return result;
103   }
104   initialized = true;
105   // Register de-initialization.
106   const int error = atexit(numaExit);
107   if (error) {
108     result = NUMAAPI_ERROR_ATEXIT;
109     return result;
110   }
111   // Load library.
112   kernel_lib = LoadLibraryA("Kernel32.dll");
113   // Load symbols.
114
115 #define _LIBRARY_FIND(lib, name)                   \
116   do {                                             \
117     _##name = (t_##name *)GetProcAddress(lib, #name);  \
118   } while (0)
119 #define KERNEL_LIBRARY_FIND(name) _LIBRARY_FIND(kernel_lib, name)
120
121   // NUMA.
122   KERNEL_LIBRARY_FIND(GetNumaHighestNodeNumber);
123   KERNEL_LIBRARY_FIND(GetNumaNodeProcessorMask);
124   KERNEL_LIBRARY_FIND(GetNumaNodeProcessorMaskEx);
125   KERNEL_LIBRARY_FIND(GetNumaProcessorNode);
126   KERNEL_LIBRARY_FIND(VirtualAllocExNuma);
127   KERNEL_LIBRARY_FIND(VirtualFree);
128   // Threading.
129   KERNEL_LIBRARY_FIND(SetProcessAffinityMask);
130   KERNEL_LIBRARY_FIND(SetThreadGroupAffinity);
131   KERNEL_LIBRARY_FIND(GetCurrentProcessorNumber);
132
133 #undef KERNEL_LIBRARY_FIND
134 #undef _LIBRARY_FIND
135
136   result = NUMAAPI_SUCCESS;
137   return result;
138 }
139
140 NUMAAPI_Result numaAPI_Initialize(void) {
141 #if !ARCH_CPU_64_BITS
142   // No NUMA on 32 bit platforms.
143   return LIBNUMAAPI_NOT_AVAILABLE;
144 #else
145   if (!IsWindows7OrGreater()) {
146     // Require Windows 7 or higher.
147     NUMAAPI_NOT_AVAILABLE;
148   }
149   loadNumaSymbols();
150   return NUMAAPI_SUCCESS;
151 #endif
152 }
153
154 ////////////////////////////////////////////////////////////////////////////////
155 // Topology query.
156
157 int numaAPI_GetNumNodes(void) {
158   ULONG highest_node_number;
159   if (!_GetNumaHighestNodeNumber(&highest_node_number)) {
160     return 0;
161   }
162   // TODO(sergey): Resolve the type narrowing.
163   // NOTE: This is not necessarily a total amount of nodes in the system.
164   return (int)highest_node_number + 1;
165 }
166
167 bool numaAPI_IsNodeAvailable(int node) {
168   // Trick to detect whether the node is usable or not: check whether
169   // there are any processors associated with it.
170   //
171   // This is needed because numaApiGetNumNodes() is not guaranteed to
172   // give total amount of nodes and some nodes might be unavailable.
173   ULONGLONG processor_mask;
174   if (!_GetNumaNodeProcessorMask(node, &processor_mask)) {
175     return false;
176   }
177   if (processor_mask == 0) {
178     return false;
179   }
180   return true;
181 }
182
183 int numaAPI_GetNumNodeProcessors(int node) {
184   ULONGLONG processor_mask;
185   if (!_GetNumaNodeProcessorMask(node, &processor_mask)) {
186     return 0;
187   }
188   // TODO(sergey): There might be faster way calculating number of set bits.
189   int num_processors = 0;
190   while (processor_mask != 0) {
191     num_processors += (processor_mask & 1);
192     processor_mask = (processor_mask >> 1);
193   }
194   return num_processors;
195 }
196
197 ////////////////////////////////////////////////////////////////////////////////
198 // Affinities.
199
200 bool numaAPI_RunProcessOnNode(int node) {
201   // TODO(sergey): Make sure requested node is within active CPU group.
202   // Change affinity of the proces to make it to run on a given node.
203   HANDLE process_handle = GetCurrentProcess();
204   ULONGLONG processor_mask;
205   if (_GetNumaNodeProcessorMask(node, &processor_mask) == 0) {
206     return false;
207   }
208   if (_SetProcessAffinityMask(process_handle, processor_mask) == 0) {
209     return false;
210   }
211   return true;
212 }
213
214 bool numaAPI_RunThreadOnNode(int node) {
215   HANDLE thread_handle = GetCurrentThread();
216   GROUP_AFFINITY group_affinity = { 0 };
217   if (_GetNumaNodeProcessorMaskEx(node, &group_affinity) == 0) {
218     return false;
219   }
220   if (_SetThreadGroupAffinity(thread_handle, &group_affinity, NULL) == 0) {
221     return false;
222   }
223   return true;
224 }
225
226 ////////////////////////////////////////////////////////////////////////////////
227 // Memory management.
228
229 void* numaAPI_AllocateOnNode(size_t size, int node) {
230   return _VirtualAllocExNuma(GetCurrentProcess(),
231                              NULL,
232                              size,
233                              MEM_RESERVE | MEM_COMMIT,
234                              PAGE_READWRITE,
235                              node);
236 }
237
238 void* numaAPI_AllocateLocal(size_t size) {
239   UCHAR current_processor = (UCHAR)_GetCurrentProcessorNumber();
240   UCHAR node;
241   if (!_GetNumaProcessorNode(current_processor, &node)) {
242     return NULL;
243   }
244   return numaAPI_AllocateOnNode(size, node);
245 }
246
247 void numaAPI_Free(void* start, size_t size) {
248   if (!_VirtualFree(start, size, MEM_RELEASE)) {
249     // TODO(sergey): Throw an error!
250   }
251 }
252
253 #endif  // OS_WIN