#ifndef SHM_H_INCLUDED
#define SHM_H_INCLUDED
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#include <iomanip>
#include <iostream>
#include <memory>
#include <new>
#include <optional>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#if !defined(_WIN32) && !defined(__ANDROID__)
#include "shm_linux.h"
#endif
#if defined(__ANDROID__)
#include <limits.h>
#define SF_MAX_SEM_NAME_LEN NAME_MAX
#endif
#include "types.h"
#include "memory.h"
#if defined(_WIN32)
#if _WIN32_WINNT < 0x0601
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0601
#endif
#if !defined(NOMINMAX)
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <cstring>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#include <sys/syslimits.h>
#elif defined(__sun)
#include <stdlib.h>
#elif defined(__FreeBSD__)
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>
#elif defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__)
#include <limits.h>
#include <unistd.h>
#endif
namespace Stockfish {
inline std::string getExecutablePathHash() {
char executable_path[4096] = {0};
std::size_t path_length = 0;
#if defined(_WIN32)
path_length = GetModuleFileNameA(NULL, executable_path, sizeof(executable_path));
#elif defined(__APPLE__)
uint32_t size = sizeof(executable_path);
if (_NSGetExecutablePath(executable_path, &size) == 0)
{
path_length = std::strlen(executable_path);
}
#elif defined(__sun)
const char* path = getexecname();
if (path)
{
std::strncpy(executable_path, path, sizeof(executable_path) - 1);
path_length = std::strlen(executable_path);
}
#elif defined(__FreeBSD__)
size_t size = sizeof(executable_path);
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
if (sysctl(mib, 4, executable_path, &size, NULL, 0) == 0)
{
path_length = std::strlen(executable_path);
}
#elif defined(__NetBSD__) || defined(__DragonFly__)
ssize_t len = readlink("/proc/curproc/exe", executable_path, sizeof(executable_path) - 1);
if (len >= 0)
{
executable_path[len] = '\0';
path_length = len;
}
#elif defined(__linux__)
ssize_t len = readlink("/proc/self/exe", executable_path, sizeof(executable_path) - 1);
if (len >= 0)
{
executable_path[len] = '\0';
path_length = len;
}
#endif
return std::string(executable_path, path_length);
}
enum class SystemWideSharedConstantAllocationStatus {
NoAllocation,
LocalMemory,
SharedMemory
};
#if defined(_WIN32)
inline std::string GetLastErrorAsString(DWORD error) {
DWORD errorMessageID = error;
if (errorMessageID == 0)
{
return std::string();
}
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR) &messageBuffer, 0, NULL);
std::string message(messageBuffer, size);
LocalFree(messageBuffer);
return message;
}
template<typename T>
class SharedMemoryBackend {
public:
enum class Status {
Success,
LargePageAllocationError,
FileMappingError,
MapViewError,
MutexCreateError,
MutexWaitError,
MutexReleaseError,
NotInitialized
};
static constexpr DWORD IS_INITIALIZED_VALUE = 1;
SharedMemoryBackend() :
status(Status::NotInitialized) {};
SharedMemoryBackend(const std::string& shm_name, const T& value) :
status(Status::NotInitialized) {
initialize(shm_name, value);
}
bool is_valid() const { return status == Status::Success; }
std::optional<std::string> get_error_message() const {
switch (status)
{
case Status::Success :
return std::nullopt;
case Status::LargePageAllocationError :
return "Failed to allocate large page memory";
case Status::FileMappingError :
return "Failed to create file mapping: " + last_error_message;
case Status::MapViewError :
return "Failed to map view: " + last_error_message;
case Status::MutexCreateError :
return "Failed to create mutex: " + last_error_message;
case Status::MutexWaitError :
return "Failed to wait on mutex: " + last_error_message;
case Status::MutexReleaseError :
return "Failed to release mutex: " + last_error_message;
case Status::NotInitialized :
return "Not initialized";
default :
return "Unknown error";
}
}
void* get() const { return is_valid() ? pMap : nullptr; }
~SharedMemoryBackend() { cleanup(); }
SharedMemoryBackend(const SharedMemoryBackend&) = delete;
SharedMemoryBackend& operator=(const SharedMemoryBackend&) = delete;
SharedMemoryBackend(SharedMemoryBackend&& other) noexcept :
pMap(other.pMap),
hMapFile(other.hMapFile),
status(other.status),
last_error_message(std::move(other.last_error_message)) {
other.pMap = nullptr;
other.hMapFile = 0;
other.status = Status::NotInitialized;
}
SharedMemoryBackend& operator=(SharedMemoryBackend&& other) noexcept {
if (this != &other)
{
cleanup();
pMap = other.pMap;
hMapFile = other.hMapFile;
status = other.status;
last_error_message = std::move(other.last_error_message);
other.pMap = nullptr;
other.hMapFile = 0;
other.status = Status::NotInitialized;
}
return *this;
}
SystemWideSharedConstantAllocationStatus get_status() const {
return status == Status::Success ? SystemWideSharedConstantAllocationStatus::SharedMemory
: SystemWideSharedConstantAllocationStatus::NoAllocation;
}
private:
void initialize(const std::string& shm_name, const T& value) {
const size_t total_size = sizeof(T) + sizeof(IS_INITIALIZED_VALUE);
hMapFile = windows_try_with_large_page_priviliges(
[&](size_t largePageSize) {
const size_t total_size_aligned =
(total_size + largePageSize - 1) / largePageSize * largePageSize;
#if defined(_WIN64)
DWORD total_size_low = total_size_aligned & 0xFFFFFFFFu;
DWORD total_size_high = total_size_aligned >> 32u;
#else
DWORD total_size_low = total_size_aligned;
DWORD total_size_high = 0;
#endif
return CreateFileMappingA(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES,
total_size_high, total_size_low, shm_name.c_str());
},
[]() { return (void*) nullptr; });
if (!hMapFile)
{
hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
static_cast<DWORD>(total_size), shm_name.c_str());
}
if (!hMapFile)
{
const DWORD err = GetLastError();
last_error_message = GetLastErrorAsString(err);
status = Status::FileMappingError;
return;
}
pMap = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, total_size);
if (!pMap)
{
const DWORD err = GetLastError();
last_error_message = GetLastErrorAsString(err);
status = Status::MapViewError;
cleanup_partial();
return;
}
std::string mutex_name = shm_name + "$mutex";
HANDLE hMutex = CreateMutexA(NULL, FALSE, mutex_name.c_str());
if (!hMutex)
{
const DWORD err = GetLastError();
last_error_message = GetLastErrorAsString(err);
status = Status::MutexCreateError;
cleanup_partial();
return;
}
DWORD wait_result = WaitForSingleObject(hMutex, INFINITE);
if (wait_result != WAIT_OBJECT_0)
{
const DWORD err = GetLastError();
last_error_message = GetLastErrorAsString(err);
status = Status::MutexWaitError;
CloseHandle(hMutex);
cleanup_partial();
return;
}
volatile DWORD* is_initialized =
std::launder(reinterpret_cast<DWORD*>(reinterpret_cast<char*>(pMap) + sizeof(T)));
T* object = std::launder(reinterpret_cast<T*>(pMap));
if (*is_initialized != IS_INITIALIZED_VALUE)
{
new (object) T{value};
*is_initialized = IS_INITIALIZED_VALUE;
}
BOOL release_result = ReleaseMutex(hMutex);
CloseHandle(hMutex);
if (!release_result)
{
const DWORD err = GetLastError();
last_error_message = GetLastErrorAsString(err);
status = Status::MutexReleaseError;
cleanup_partial();
return;
}
status = Status::Success;
}
void cleanup_partial() {
if (pMap != nullptr)
{
UnmapViewOfFile(pMap);
pMap = nullptr;
}
if (hMapFile)
{
CloseHandle(hMapFile);
hMapFile = 0;
}
}
void cleanup() {
if (pMap != nullptr)
{
UnmapViewOfFile(pMap);
pMap = nullptr;
}
if (hMapFile)
{
CloseHandle(hMapFile);
hMapFile = 0;
}
}
void* pMap = nullptr;
HANDLE hMapFile = 0;
Status status = Status::NotInitialized;
std::string last_error_message;
};
#elif !defined(__ANDROID__)
template<typename T>
class SharedMemoryBackend {
public:
SharedMemoryBackend() = default;
SharedMemoryBackend(const std::string& shm_name, const T& value) :
shm1(shm::create_shared<T>(shm_name, value)) {}
void* get() const {
const T* ptr = &shm1->get();
return reinterpret_cast<void*>(const_cast<T*>(ptr));
}
bool is_valid() const { return shm1 && shm1->is_open() && shm1->is_initialized(); }
SystemWideSharedConstantAllocationStatus get_status() const {
return is_valid() ? SystemWideSharedConstantAllocationStatus::SharedMemory
: SystemWideSharedConstantAllocationStatus::NoAllocation;
}
std::optional<std::string> get_error_message() const {
if (!shm1)
return "Shared memory not initialized";
if (!shm1->is_open())
return "Shared memory is not open";
if (!shm1->is_initialized())
return "Not initialized";
return std::nullopt;
}
private:
std::optional<shm::SharedMemory<T>> shm1;
};
#else
template<typename T>
class SharedMemoryBackend {
public:
SharedMemoryBackend() = default;
SharedMemoryBackend([[maybe_unused]] const std::string& shm_name,
[[maybe_unused]] const T& value) {}
void* get() const { return nullptr; }
bool is_valid() const { return false; }
SystemWideSharedConstantAllocationStatus get_status() const {
return SystemWideSharedConstantAllocationStatus::NoAllocation;
}
std::optional<std::string> get_error_message() const { return "Dummy SharedMemoryBackend"; }
};
#endif
template<typename T>
struct SharedMemoryBackendFallback {
SharedMemoryBackendFallback() = default;
SharedMemoryBackendFallback(const std::string&, const T& value) :
fallback_object(make_unique_large_page<T>(value)) {}
void* get() const { return fallback_object.get(); }
SharedMemoryBackendFallback(const SharedMemoryBackendFallback&) = delete;
SharedMemoryBackendFallback& operator=(const SharedMemoryBackendFallback&) = delete;
SharedMemoryBackendFallback(SharedMemoryBackendFallback&& other) noexcept :
fallback_object(std::move(other.fallback_object)) {}
SharedMemoryBackendFallback& operator=(SharedMemoryBackendFallback&& other) noexcept {
fallback_object = std::move(other.fallback_object);
return *this;
}
SystemWideSharedConstantAllocationStatus get_status() const {
return fallback_object == nullptr ? SystemWideSharedConstantAllocationStatus::NoAllocation
: SystemWideSharedConstantAllocationStatus::LocalMemory;
}
std::optional<std::string> get_error_message() const {
if (fallback_object == nullptr)
return "Not initialized";
return "Shared memory not supported by the OS. Local allocation fallback.";
}
private:
LargePagePtr<T> fallback_object;
};
template<typename T>
struct SystemWideSharedConstant {
private:
static std::string createHashString(const std::string& input) {
size_t hash = std::hash<std::string>{}(input);
std::stringstream ss;
ss << std::hex << std::setfill('0') << hash;
return ss.str();
}
public:
static_assert(std::is_trivially_destructible_v<T>);
static_assert(std::is_trivially_move_constructible_v<T>);
static_assert(std::is_trivially_copy_constructible_v<T>);
SystemWideSharedConstant() = default;
SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) {
std::size_t content_hash = std::hash<T>{}(value);
std::size_t executable_hash = std::hash<std::string>{}(getExecutablePathHash());
std::string shm_name = std::string("Local\\sf_") + std::to_string(content_hash) + "$"
+ std::to_string(executable_hash) + "$"
+ std::to_string(discriminator);
#if !defined(_WIN32)
shm_name = "/sf_" + createHashString(shm_name);
if (shm_name.size() > SF_MAX_SEM_NAME_LEN)
{
shm_name = shm_name.substr(0, SF_MAX_SEM_NAME_LEN - 1);
}
#endif
SharedMemoryBackend<T> shm_backend(shm_name, value);
if (shm_backend.is_valid())
{
backend = std::move(shm_backend);
}
else
{
backend = SharedMemoryBackendFallback<T>(shm_name, value);
}
}
SystemWideSharedConstant(const SystemWideSharedConstant&) = delete;
SystemWideSharedConstant& operator=(const SystemWideSharedConstant&) = delete;
SystemWideSharedConstant(SystemWideSharedConstant&& other) noexcept :
backend(std::move(other.backend)) {}
SystemWideSharedConstant& operator=(SystemWideSharedConstant&& other) noexcept {
backend = std::move(other.backend);
return *this;
}
const T& operator*() const { return *std::launder(reinterpret_cast<const T*>(get_ptr())); }
bool operator==(std::nullptr_t) const noexcept { return get_ptr() == nullptr; }
bool operator!=(std::nullptr_t) const noexcept { return get_ptr() != nullptr; }
SystemWideSharedConstantAllocationStatus get_status() const {
return std::visit(
[](const auto& end) -> SystemWideSharedConstantAllocationStatus {
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
{
return SystemWideSharedConstantAllocationStatus::NoAllocation;
}
else
{
return end.get_status();
}
},
backend);
}
std::optional<std::string> get_error_message() const {
return std::visit(
[](const auto& end) -> std::optional<std::string> {
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
{
return std::nullopt;
}
else
{
return end.get_error_message();
}
},
backend);
}
private:
auto get_ptr() const {
return std::visit(
[](const auto& end) -> void* {
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
{
return nullptr;
}
else
{
return end.get();
}
},
backend);
}
std::variant<std::monostate, SharedMemoryBackend<T>, SharedMemoryBackendFallback<T>> backend;
};
}
#endif