Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
official-stockfish
GitHub Repository: official-stockfish/Stockfish
Path: blob/master/src/shm.h
473 views
1
/*
2
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
3
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
4
5
Stockfish is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9
10
Stockfish is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
14
15
You should have received a copy of the GNU General Public License
16
along with this program. If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
#ifndef SHM_H_INCLUDED
20
#define SHM_H_INCLUDED
21
22
#include <algorithm>
23
#include <cstddef>
24
#include <cstdint>
25
#include <cstring>
26
#include <functional>
27
#include <iomanip>
28
#include <iostream>
29
#include <memory>
30
#include <new>
31
#include <optional>
32
#include <sstream>
33
#include <string>
34
#include <type_traits>
35
#include <utility>
36
#include <variant>
37
38
#if !defined(_WIN32) && !defined(__ANDROID__)
39
#include "shm_linux.h"
40
#endif
41
42
#if defined(__ANDROID__)
43
#include <limits.h>
44
#define SF_MAX_SEM_NAME_LEN NAME_MAX
45
#endif
46
47
#include "types.h"
48
49
#include "memory.h"
50
51
#if defined(_WIN32)
52
53
#if _WIN32_WINNT < 0x0601
54
#undef _WIN32_WINNT
55
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
56
#endif
57
58
#if !defined(NOMINMAX)
59
#define NOMINMAX
60
#endif
61
#include <windows.h>
62
#else
63
#include <cstring>
64
#include <fcntl.h>
65
#include <pthread.h>
66
#include <semaphore.h>
67
#include <sys/mman.h>
68
#include <sys/stat.h>
69
#include <unistd.h>
70
#endif
71
72
73
#if defined(__APPLE__)
74
#include <mach-o/dyld.h>
75
#include <sys/syslimits.h>
76
77
#elif defined(__sun)
78
#include <stdlib.h>
79
80
#elif defined(__FreeBSD__)
81
#include <sys/sysctl.h>
82
#include <sys/types.h>
83
#include <unistd.h>
84
85
#elif defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__)
86
#include <limits.h>
87
#include <unistd.h>
88
#endif
89
90
91
namespace Stockfish {
92
93
// argv[0] CANNOT be used because we need to identify the executable.
94
// argv[0] contains the command used to invoke it, which does not involve the full path.
95
// Just using a path is not fully resilient either, as the executable could
96
// have changed if it wasn't locked by the OS. Ideally we would hash the executable
97
// but it's not really that important at this point.
98
// If the path is longer than 4095 bytes the hash will be computed from an unspecified
99
// amount of bytes of the path; in particular it can a hash of an empty string.
100
101
inline std::string getExecutablePathHash() {
102
char executable_path[4096] = {0};
103
std::size_t path_length = 0;
104
105
#if defined(_WIN32)
106
path_length = GetModuleFileNameA(NULL, executable_path, sizeof(executable_path));
107
108
#elif defined(__APPLE__)
109
uint32_t size = sizeof(executable_path);
110
if (_NSGetExecutablePath(executable_path, &size) == 0)
111
{
112
path_length = std::strlen(executable_path);
113
}
114
115
#elif defined(__sun) // Solaris
116
const char* path = getexecname();
117
if (path)
118
{
119
std::strncpy(executable_path, path, sizeof(executable_path) - 1);
120
path_length = std::strlen(executable_path);
121
}
122
123
#elif defined(__FreeBSD__)
124
size_t size = sizeof(executable_path);
125
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
126
if (sysctl(mib, 4, executable_path, &size, NULL, 0) == 0)
127
{
128
path_length = std::strlen(executable_path);
129
}
130
131
#elif defined(__NetBSD__) || defined(__DragonFly__)
132
ssize_t len = readlink("/proc/curproc/exe", executable_path, sizeof(executable_path) - 1);
133
if (len >= 0)
134
{
135
executable_path[len] = '\0';
136
path_length = len;
137
}
138
139
#elif defined(__linux__)
140
ssize_t len = readlink("/proc/self/exe", executable_path, sizeof(executable_path) - 1);
141
if (len >= 0)
142
{
143
executable_path[len] = '\0';
144
path_length = len;
145
}
146
147
#endif
148
149
// In case of any error the path will be empty.
150
return std::string(executable_path, path_length);
151
}
152
153
enum class SystemWideSharedConstantAllocationStatus {
154
NoAllocation,
155
LocalMemory,
156
SharedMemory
157
};
158
159
#if defined(_WIN32)
160
161
inline std::string GetLastErrorAsString(DWORD error) {
162
//Get the error message ID, if any.
163
DWORD errorMessageID = error;
164
if (errorMessageID == 0)
165
{
166
return std::string(); //No error message has been recorded
167
}
168
169
LPSTR messageBuffer = nullptr;
170
171
//Ask Win32 to give us the string version of that message ID.
172
//The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).
173
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
174
| FORMAT_MESSAGE_IGNORE_INSERTS,
175
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
176
(LPSTR) &messageBuffer, 0, NULL);
177
178
//Copy the error message into a std::string.
179
std::string message(messageBuffer, size);
180
181
//Free the Win32's string's buffer.
182
LocalFree(messageBuffer);
183
184
return message;
185
}
186
187
// Utilizes shared memory to store the value. It is deduplicated system-wide (for the single user).
188
template<typename T>
189
class SharedMemoryBackend {
190
public:
191
enum class Status {
192
Success,
193
LargePageAllocationError,
194
FileMappingError,
195
MapViewError,
196
MutexCreateError,
197
MutexWaitError,
198
MutexReleaseError,
199
NotInitialized
200
};
201
202
static constexpr DWORD IS_INITIALIZED_VALUE = 1;
203
204
SharedMemoryBackend() :
205
status(Status::NotInitialized) {};
206
207
SharedMemoryBackend(const std::string& shm_name, const T& value) :
208
status(Status::NotInitialized) {
209
210
initialize(shm_name, value);
211
}
212
213
bool is_valid() const { return status == Status::Success; }
214
215
std::optional<std::string> get_error_message() const {
216
switch (status)
217
{
218
case Status::Success :
219
return std::nullopt;
220
case Status::LargePageAllocationError :
221
return "Failed to allocate large page memory";
222
case Status::FileMappingError :
223
return "Failed to create file mapping: " + last_error_message;
224
case Status::MapViewError :
225
return "Failed to map view: " + last_error_message;
226
case Status::MutexCreateError :
227
return "Failed to create mutex: " + last_error_message;
228
case Status::MutexWaitError :
229
return "Failed to wait on mutex: " + last_error_message;
230
case Status::MutexReleaseError :
231
return "Failed to release mutex: " + last_error_message;
232
case Status::NotInitialized :
233
return "Not initialized";
234
default :
235
return "Unknown error";
236
}
237
}
238
239
void* get() const { return is_valid() ? pMap : nullptr; }
240
241
~SharedMemoryBackend() { cleanup(); }
242
243
SharedMemoryBackend(const SharedMemoryBackend&) = delete;
244
SharedMemoryBackend& operator=(const SharedMemoryBackend&) = delete;
245
246
SharedMemoryBackend(SharedMemoryBackend&& other) noexcept :
247
pMap(other.pMap),
248
hMapFile(other.hMapFile),
249
status(other.status),
250
last_error_message(std::move(other.last_error_message)) {
251
252
other.pMap = nullptr;
253
other.hMapFile = 0;
254
other.status = Status::NotInitialized;
255
}
256
257
SharedMemoryBackend& operator=(SharedMemoryBackend&& other) noexcept {
258
if (this != &other)
259
{
260
cleanup();
261
pMap = other.pMap;
262
hMapFile = other.hMapFile;
263
status = other.status;
264
last_error_message = std::move(other.last_error_message);
265
266
other.pMap = nullptr;
267
other.hMapFile = 0;
268
other.status = Status::NotInitialized;
269
}
270
return *this;
271
}
272
273
SystemWideSharedConstantAllocationStatus get_status() const {
274
return status == Status::Success ? SystemWideSharedConstantAllocationStatus::SharedMemory
275
: SystemWideSharedConstantAllocationStatus::NoAllocation;
276
}
277
278
private:
279
void initialize(const std::string& shm_name, const T& value) {
280
const size_t total_size = sizeof(T) + sizeof(IS_INITIALIZED_VALUE);
281
282
// Try allocating with large pages first.
283
hMapFile = windows_try_with_large_page_priviliges(
284
[&](size_t largePageSize) {
285
const size_t total_size_aligned =
286
(total_size + largePageSize - 1) / largePageSize * largePageSize;
287
288
#if defined(_WIN64)
289
DWORD total_size_low = total_size_aligned & 0xFFFFFFFFu;
290
DWORD total_size_high = total_size_aligned >> 32u;
291
#else
292
DWORD total_size_low = total_size_aligned;
293
DWORD total_size_high = 0;
294
#endif
295
296
return CreateFileMappingA(INVALID_HANDLE_VALUE, NULL,
297
PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES,
298
total_size_high, total_size_low, shm_name.c_str());
299
},
300
[]() { return (void*) nullptr; });
301
302
// Fallback to normal allocation if no large pages available.
303
if (!hMapFile)
304
{
305
hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
306
static_cast<DWORD>(total_size), shm_name.c_str());
307
}
308
309
if (!hMapFile)
310
{
311
const DWORD err = GetLastError();
312
last_error_message = GetLastErrorAsString(err);
313
status = Status::FileMappingError;
314
return;
315
}
316
317
pMap = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, total_size);
318
if (!pMap)
319
{
320
const DWORD err = GetLastError();
321
last_error_message = GetLastErrorAsString(err);
322
status = Status::MapViewError;
323
cleanup_partial();
324
return;
325
}
326
327
// Use named mutex to ensure only one initializer
328
std::string mutex_name = shm_name + "$mutex";
329
HANDLE hMutex = CreateMutexA(NULL, FALSE, mutex_name.c_str());
330
if (!hMutex)
331
{
332
const DWORD err = GetLastError();
333
last_error_message = GetLastErrorAsString(err);
334
status = Status::MutexCreateError;
335
cleanup_partial();
336
return;
337
}
338
339
DWORD wait_result = WaitForSingleObject(hMutex, INFINITE);
340
if (wait_result != WAIT_OBJECT_0)
341
{
342
const DWORD err = GetLastError();
343
last_error_message = GetLastErrorAsString(err);
344
status = Status::MutexWaitError;
345
CloseHandle(hMutex);
346
cleanup_partial();
347
return;
348
}
349
350
// Crucially, we place the object first to ensure alignment.
351
volatile DWORD* is_initialized =
352
std::launder(reinterpret_cast<DWORD*>(reinterpret_cast<char*>(pMap) + sizeof(T)));
353
T* object = std::launder(reinterpret_cast<T*>(pMap));
354
355
if (*is_initialized != IS_INITIALIZED_VALUE)
356
{
357
// First time initialization, message for debug purposes
358
new (object) T{value};
359
*is_initialized = IS_INITIALIZED_VALUE;
360
}
361
362
BOOL release_result = ReleaseMutex(hMutex);
363
CloseHandle(hMutex);
364
365
if (!release_result)
366
{
367
const DWORD err = GetLastError();
368
last_error_message = GetLastErrorAsString(err);
369
status = Status::MutexReleaseError;
370
cleanup_partial();
371
return;
372
}
373
374
status = Status::Success;
375
}
376
377
void cleanup_partial() {
378
if (pMap != nullptr)
379
{
380
UnmapViewOfFile(pMap);
381
pMap = nullptr;
382
}
383
if (hMapFile)
384
{
385
CloseHandle(hMapFile);
386
hMapFile = 0;
387
}
388
}
389
390
void cleanup() {
391
if (pMap != nullptr)
392
{
393
UnmapViewOfFile(pMap);
394
pMap = nullptr;
395
}
396
if (hMapFile)
397
{
398
CloseHandle(hMapFile);
399
hMapFile = 0;
400
}
401
}
402
403
void* pMap = nullptr;
404
HANDLE hMapFile = 0;
405
Status status = Status::NotInitialized;
406
std::string last_error_message;
407
};
408
409
#elif !defined(__ANDROID__)
410
411
template<typename T>
412
class SharedMemoryBackend {
413
public:
414
SharedMemoryBackend() = default;
415
416
SharedMemoryBackend(const std::string& shm_name, const T& value) :
417
shm1(shm::create_shared<T>(shm_name, value)) {}
418
419
void* get() const {
420
const T* ptr = &shm1->get();
421
return reinterpret_cast<void*>(const_cast<T*>(ptr));
422
}
423
424
bool is_valid() const { return shm1 && shm1->is_open() && shm1->is_initialized(); }
425
426
SystemWideSharedConstantAllocationStatus get_status() const {
427
return is_valid() ? SystemWideSharedConstantAllocationStatus::SharedMemory
428
: SystemWideSharedConstantAllocationStatus::NoAllocation;
429
}
430
431
std::optional<std::string> get_error_message() const {
432
if (!shm1)
433
return "Shared memory not initialized";
434
435
if (!shm1->is_open())
436
return "Shared memory is not open";
437
438
if (!shm1->is_initialized())
439
return "Not initialized";
440
441
return std::nullopt;
442
}
443
444
private:
445
std::optional<shm::SharedMemory<T>> shm1;
446
};
447
448
#else
449
450
// For systems that don't have shared memory, or support is troublesome.
451
// The way fallback is done is that we need a dummy backend.
452
453
template<typename T>
454
class SharedMemoryBackend {
455
public:
456
SharedMemoryBackend() = default;
457
458
SharedMemoryBackend([[maybe_unused]] const std::string& shm_name,
459
[[maybe_unused]] const T& value) {}
460
461
void* get() const { return nullptr; }
462
463
bool is_valid() const { return false; }
464
465
SystemWideSharedConstantAllocationStatus get_status() const {
466
return SystemWideSharedConstantAllocationStatus::NoAllocation;
467
}
468
469
std::optional<std::string> get_error_message() const { return "Dummy SharedMemoryBackend"; }
470
};
471
472
#endif
473
474
template<typename T>
475
struct SharedMemoryBackendFallback {
476
SharedMemoryBackendFallback() = default;
477
478
SharedMemoryBackendFallback(const std::string&, const T& value) :
479
fallback_object(make_unique_large_page<T>(value)) {}
480
481
void* get() const { return fallback_object.get(); }
482
483
SharedMemoryBackendFallback(const SharedMemoryBackendFallback&) = delete;
484
SharedMemoryBackendFallback& operator=(const SharedMemoryBackendFallback&) = delete;
485
486
SharedMemoryBackendFallback(SharedMemoryBackendFallback&& other) noexcept :
487
fallback_object(std::move(other.fallback_object)) {}
488
489
SharedMemoryBackendFallback& operator=(SharedMemoryBackendFallback&& other) noexcept {
490
fallback_object = std::move(other.fallback_object);
491
return *this;
492
}
493
494
SystemWideSharedConstantAllocationStatus get_status() const {
495
return fallback_object == nullptr ? SystemWideSharedConstantAllocationStatus::NoAllocation
496
: SystemWideSharedConstantAllocationStatus::LocalMemory;
497
}
498
499
std::optional<std::string> get_error_message() const {
500
if (fallback_object == nullptr)
501
return "Not initialized";
502
503
return "Shared memory not supported by the OS. Local allocation fallback.";
504
}
505
506
private:
507
LargePagePtr<T> fallback_object;
508
};
509
510
// Platform-independent wrapper
511
template<typename T>
512
struct SystemWideSharedConstant {
513
private:
514
static std::string createHashString(const std::string& input) {
515
size_t hash = std::hash<std::string>{}(input);
516
517
std::stringstream ss;
518
ss << std::hex << std::setfill('0') << hash;
519
520
return ss.str();
521
}
522
523
public:
524
// We can't run the destructor because it may be in a completely different process.
525
// The object stored must also be obviously in-line but we can't check for that, other than some basic checks that cover most cases.
526
static_assert(std::is_trivially_destructible_v<T>);
527
static_assert(std::is_trivially_move_constructible_v<T>);
528
static_assert(std::is_trivially_copy_constructible_v<T>);
529
530
SystemWideSharedConstant() = default;
531
532
533
// Content is addressed by its hash. An additional discriminator can be added to account for differences
534
// that are not present in the content, for example NUMA node allocation.
535
SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) {
536
std::size_t content_hash = std::hash<T>{}(value);
537
std::size_t executable_hash = std::hash<std::string>{}(getExecutablePathHash());
538
539
std::string shm_name = std::string("Local\\sf_") + std::to_string(content_hash) + "$"
540
+ std::to_string(executable_hash) + "$"
541
+ std::to_string(discriminator);
542
543
#if !defined(_WIN32)
544
// POSIX shared memory names must start with a slash
545
shm_name = "/sf_" + createHashString(shm_name);
546
547
// hash name and make sure it is not longer than SF_MAX_SEM_NAME_LEN
548
if (shm_name.size() > SF_MAX_SEM_NAME_LEN)
549
{
550
shm_name = shm_name.substr(0, SF_MAX_SEM_NAME_LEN - 1);
551
}
552
#endif
553
554
SharedMemoryBackend<T> shm_backend(shm_name, value);
555
556
if (shm_backend.is_valid())
557
{
558
backend = std::move(shm_backend);
559
}
560
else
561
{
562
backend = SharedMemoryBackendFallback<T>(shm_name, value);
563
}
564
}
565
566
SystemWideSharedConstant(const SystemWideSharedConstant&) = delete;
567
SystemWideSharedConstant& operator=(const SystemWideSharedConstant&) = delete;
568
569
SystemWideSharedConstant(SystemWideSharedConstant&& other) noexcept :
570
backend(std::move(other.backend)) {}
571
572
SystemWideSharedConstant& operator=(SystemWideSharedConstant&& other) noexcept {
573
backend = std::move(other.backend);
574
return *this;
575
}
576
577
const T& operator*() const { return *std::launder(reinterpret_cast<const T*>(get_ptr())); }
578
579
bool operator==(std::nullptr_t) const noexcept { return get_ptr() == nullptr; }
580
581
bool operator!=(std::nullptr_t) const noexcept { return get_ptr() != nullptr; }
582
583
SystemWideSharedConstantAllocationStatus get_status() const {
584
return std::visit(
585
[](const auto& end) -> SystemWideSharedConstantAllocationStatus {
586
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
587
{
588
return SystemWideSharedConstantAllocationStatus::NoAllocation;
589
}
590
else
591
{
592
return end.get_status();
593
}
594
},
595
backend);
596
}
597
598
std::optional<std::string> get_error_message() const {
599
return std::visit(
600
[](const auto& end) -> std::optional<std::string> {
601
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
602
{
603
return std::nullopt;
604
}
605
else
606
{
607
return end.get_error_message();
608
}
609
},
610
backend);
611
}
612
613
private:
614
auto get_ptr() const {
615
return std::visit(
616
[](const auto& end) -> void* {
617
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
618
{
619
return nullptr;
620
}
621
else
622
{
623
return end.get();
624
}
625
},
626
backend);
627
}
628
629
std::variant<std::monostate, SharedMemoryBackend<T>, SharedMemoryBackendFallback<T>> backend;
630
};
631
632
633
} // namespace Stockfish
634
635
#endif // #ifndef SHM_H_INCLUDED
636
637