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