Compare commits

...

36 Commits

Author SHA1 Message Date
木头云
5d56ef759f refactor(uninitialized): Improve construct() overload resolution
IMPROVEMENTS:
1. Add explicit zero-argument overload to avoid SFINAE ambiguity
2. Require at least one argument (A1) for parameterized overloads
3. Better separation between direct initialization and aggregate initialization

BENEFITS:
- Clearer intent: zero-argument construction is explicitly handled
- Avoids potential SFINAE ambiguity when empty parameter pack is used
- More maintainable: easier to understand which overload is selected
- Consistent with modern C++ best practices for variadic templates

TECHNICAL DETAILS:
- Zero-arg overload: Always uses T() for value initialization
- One-or-more-arg overload: Uses SFINAE to choose between:
  * T(args...) for types with matching constructor
  * T{args...} for aggregate types or types with initializer_list ctor

This is a code quality improvement and does not fix any compilation issues,
but provides better template overload resolution.
2025-12-01 09:58:53 +00:00
木头云
eede00165e fix(container_allocator): Fix MSVC compilation by correcting allocator semantics
ROOT CAUSE:
The allocate() function was incorrectly constructing objects during memory
allocation, violating C++ allocator requirements. MSVC's std::_Tree_node has
a deleted default constructor, causing compilation failure.

CHANGES:
- container_allocator::allocate() now only allocates raw memory without
  constructing objects (removed mem::$new and ipc::construct calls)
- container_allocator::deallocate() now only frees memory without
  destroying objects (removed mem::$delete and ipc::destroy_n calls)

WHY THIS FIXES THE ISSUE:
C++ allocator semantics require strict separation:
  * allocate()   -> raw memory allocation only
  * construct()  -> object construction with proper arguments
  * destroy()    -> object destruction
  * deallocate() -> memory deallocation only

Standard containers (like std::map) call construct() with proper arguments
(key, value) to initialize nodes, not allocate(). Since std::_Tree_node in
MSVC has no default constructor (= delete), attempting to construct it
without arguments always fails.

Fixes MSVC 2017 compilation error:
  error C2280: 'std::_Tree_node<...>::_Tree_node(void)':
  attempting to reference a deleted function
2025-12-01 09:24:58 +00:00
mutouyun
bb3c6eb534 Replace custom hash struct with std::hash in unordered_map definition 2025-05-24 15:14:30 +08:00
mutouyun
70121091d6 Fix the issue caused by inconsistent lifecycle of the global IPC object. 2025-05-06 17:59:03 +08:00
mutouyun
32c2be29a9 Refactoring the generic memory allocator 2025-03-09 18:02:51 +08:00
mutouyun
2d38a6a6e7 Reimplement the allocator required for the container type with $new 2025-03-09 18:02:51 +08:00
mutouyun
831225f763 Use $new instead of alloc 2025-03-09 18:02:51 +08:00
mutouyun
db109165f3 Simplify the implementation of memory allocation management 2025-03-09 18:02:51 +08:00
mutouyun
180920968f The memory allocator supports runtime dynamic size memory allocation 2025-03-09 18:02:51 +08:00
mutouyun
ea0a3a4bf6 libipc/memory/resource.h => libipc/mem/resource.h 2025-03-09 18:02:51 +08:00
mutouyun
89e9f87f36 Add $new 2025-03-09 18:02:51 +08:00
mutouyun
edc1e80585 Add block_pool 2025-03-09 18:02:51 +08:00
mutouyun
3571fa58c8 Adjust the allocator name 2025-03-09 18:02:51 +08:00
mutouyun
99272a452a Simplify verify_args function to fix error C3249 2025-03-09 18:02:51 +08:00
mutouyun
86d9d868c8 Fix fmt function to handle null pointers and return empty string 2025-03-09 18:02:51 +08:00
木头云
00162b96b9 Update c-cpp.yml 2025-03-09 18:02:51 +08:00
mutouyun
87edc6fab1 Fix fmt function to handle empty strings and update make_prefix template parameters 2025-03-09 18:02:51 +08:00
mutouyun
70a1f68f01 Optimize memory_resource & add monotonic_buffer_resource 2025-03-09 18:02:51 +08:00
mutouyun
b8d01ddf68 Add intrusive_stack 2025-03-09 18:02:51 +08:00
mutouyun
40eaab7310 Add allocator and rewrite allocator_wrapper 2025-03-09 18:02:51 +08:00
mutouyun
8c34d5d4bc Optimized partial implementation using fmt 2025-03-09 18:02:51 +08:00
mutouyun
48d4d6111d Start refactoring memory management, adding memory_resource 2025-03-09 18:02:51 +08:00
mutouyun
033f22ae8f Update platform-specific feature macros to new interfaces in imp 2025-03-09 18:02:51 +08:00
mutouyun
85bf8263fb Add system 2025-03-09 18:02:51 +08:00
mutouyun
6db5845a45 Add result 2025-03-09 18:02:51 +08:00
mutouyun
6c068f7ba4 IPC_EXPORT => LIBIPC_EXPORT 2025-03-09 18:02:51 +08:00
mutouyun
d2ba9dce52 Add log 2025-03-09 18:02:51 +08:00
mutouyun
72ec8ee42f Add error 2025-03-09 18:02:51 +08:00
mutouyun
e30ab84ccc Added fmt support for byte 2025-03-09 18:02:51 +08:00
mutouyun
e333bc754e Add fmt 2025-03-09 18:02:50 +08:00
mutouyun
a2d82ec6f0 Add codecvt 2025-03-09 18:02:50 +08:00
mutouyun
54f05e9536 libimp => libipc 2025-03-09 18:02:50 +08:00
mutouyun
e5546179e2 Add nameof & scope_exit 2025-03-09 18:02:50 +08:00
mutouyun
e5a56f02f1 Move the export.h file to the imp directory 2025-03-09 18:02:50 +08:00
mutouyun
ed8f9d06a6 Add expected 2025-03-09 18:02:50 +08:00
mutouyun
67776aea65 Add imp for subsequent refactoring 2025-03-09 18:02:50 +08:00
104 changed files with 6431 additions and 1573 deletions

View File

@ -2,7 +2,7 @@ name: C/C++ CI
on:
push:
branches: [ master, develop, issue-* ]
branches: [ master, develop, issue-*, feature/* ]
pull_request:
branches: [ master, develop ]

View File

@ -5,12 +5,12 @@
#include <vector>
#include <type_traits>
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
namespace ipc {
class IPC_EXPORT buffer {
class LIBIPC_EXPORT buffer {
public:
using destructor_t = void (*)(void*, std::size_t);
@ -57,8 +57,8 @@ public:
};
}
friend IPC_EXPORT bool operator==(buffer const & b1, buffer const & b2);
friend IPC_EXPORT bool operator!=(buffer const & b1, buffer const & b2);
friend LIBIPC_EXPORT bool operator==(buffer const & b1, buffer const & b2);
friend LIBIPC_EXPORT bool operator!=(buffer const & b1, buffer const & b2);
private:
class buffer_;

View File

@ -0,0 +1,66 @@
/**
* \file libconcur/intrusive_stack.h
* \author mutouyun (orz@orzz.org)
* \brief Define concurrent intrusive stack.
*/
#pragma once
#include <atomic>
namespace ipc {
namespace concur {
/// \brief Intrusive stack node.
/// \tparam T The type of the value.
template <typename T>
struct intrusive_node {
T value;
std::atomic<intrusive_node *> next;
};
/// \brief Intrusive stack.
/// \tparam T The type of the value.
/// \tparam Node The type of the node.
template <typename T, typename Node = intrusive_node<T>>
class intrusive_stack {
public:
using node = Node;
private:
std::atomic<node *> top_{nullptr};
public:
intrusive_stack(intrusive_stack const &) = delete;
intrusive_stack(intrusive_stack &&) = delete;
intrusive_stack &operator=(intrusive_stack const &) = delete;
intrusive_stack &operator=(intrusive_stack &&) = delete;
constexpr intrusive_stack() noexcept = default;
bool empty() const noexcept {
return top_.load(std::memory_order_acquire) == nullptr;
}
void push(node *n) noexcept {
node *old_top = top_.load(std::memory_order_acquire);
do {
n->next.store(old_top, std::memory_order_relaxed);
} while (!top_.compare_exchange_weak(old_top, n, std::memory_order_release
, std::memory_order_acquire));
}
node *pop() noexcept {
node *old_top = top_.load(std::memory_order_acquire);
do {
if (old_top == nullptr) {
return nullptr;
}
} while (!top_.compare_exchange_weak(old_top, old_top->next.load(std::memory_order_relaxed)
, std::memory_order_release
, std::memory_order_acquire));
return old_top;
}
};
} // namespace concur
} // namespace ipc

View File

@ -2,14 +2,14 @@
#include <cstdint> // std::uint64_t
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
#include "libipc/mutex.h"
namespace ipc {
namespace sync {
class IPC_EXPORT condition {
class LIBIPC_EXPORT condition {
condition(condition const &) = delete;
condition &operator=(condition const &) = delete;

View File

@ -26,25 +26,26 @@ using uint_t = typename uint<N>::type;
// constants
enum : std::uint32_t {
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
default_timeout = 100, // ms
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
default_timeout = 100, // ms
};
enum : std::size_t {
data_length = 64,
large_msg_limit = data_length,
large_msg_align = 1024,
large_msg_cache = 32,
central_cache_default_size = 1024 * 1024, ///< 1MB
data_length = 64,
large_msg_limit = data_length,
large_msg_align = 1024,
large_msg_cache = 32,
};
enum class relat { // multiplicity of the relationship
single,
multi
single,
multi
};
enum class trans { // transmission
unicast,
broadcast
unicast,
broadcast
};
// producer-consumer policy flag
@ -57,9 +58,9 @@ struct relat_trait;
template <relat Rp, relat Rc, trans Ts>
struct relat_trait<wr<Rp, Rc, Ts>> {
constexpr static bool is_multi_producer = (Rp == relat::multi);
constexpr static bool is_multi_consumer = (Rc == relat::multi);
constexpr static bool is_broadcast = (Ts == trans::broadcast);
constexpr static bool is_multi_producer = (Rp == relat::multi);
constexpr static bool is_multi_consumer = (Rc == relat::multi);
constexpr static bool is_broadcast = (Ts == trans::broadcast);
};
template <template <typename> class Policy, typename Flag>
@ -67,7 +68,7 @@ struct relat_trait<Policy<Flag>> : relat_trait<Flag> {};
// the prefix tag of a channel
struct prefix {
char const *str;
char const *str;
};
} // namespace ipc

View File

@ -1,54 +0,0 @@
#pragma once
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
# define IPC_DECL_EXPORT Q_DECL_EXPORT
# define IPC_DECL_IMPORT Q_DECL_IMPORT
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
/*
* Compiler & system detection for IPC_DECL_EXPORT & IPC_DECL_IMPORT.
* Not using QtCore cause it shouldn't depend on Qt.
*/
#if defined(_MSC_VER)
# define IPC_DECL_EXPORT __declspec(dllexport)
# define IPC_DECL_IMPORT __declspec(dllimport)
#elif defined(__ARMCC__) || defined(__CC_ARM)
# if defined(ANDROID) || defined(__linux__) || defined(__linux)
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
# else
# define IPC_DECL_EXPORT __declspec(dllexport)
# define IPC_DECL_IMPORT __declspec(dllimport)
# endif
#elif defined(__GNUC__)
# if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
# define IPC_DECL_EXPORT __declspec(dllexport)
# define IPC_DECL_IMPORT __declspec(dllimport)
# else
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
# endif
#else
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
#endif
#endif // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
/*
* Define IPC_EXPORT for exporting function & class.
*/
#ifndef IPC_EXPORT
#if defined(LIBIPC_LIBRARY_SHARED_BUILDING__)
# define IPC_EXPORT IPC_DECL_EXPORT
#elif defined(LIBIPC_LIBRARY_SHARED_USING__)
# define IPC_EXPORT IPC_DECL_IMPORT
#else
# define IPC_EXPORT
#endif
#endif /*IPC_EXPORT*/

View File

@ -0,0 +1,74 @@
/**
* \file libipc/aligned.h
* \author mutouyun (orz@orzz.org)
* \brief Defines the type suitable for use as uninitialized storage for types of given type.
*/
#pragma once
#include <array>
#include <cstddef>
#include "libipc/imp/byte.h"
namespace ipc {
/**
* \brief The type suitable for use as uninitialized storage for types of given type.
* std::aligned_storage is deprecated in C++23, so we define our own.
* \tparam T The type to be aligned.
* \tparam AlignT The alignment of the type.
* \see https://en.cppreference.com/w/cpp/types/aligned_storage
* https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf
*/
template <typename T, std::size_t AlignT = alignof(T)>
class aligned {
alignas(AlignT) std::array<ipc::byte, sizeof(T)> storage_;
public:
/**
* \brief Returns a pointer to the aligned storage.
* \return A pointer to the aligned storage.
*/
T *ptr() noexcept {
return reinterpret_cast<T *>(storage_.data());
}
/**
* \brief Returns a pointer to the aligned storage.
* \return A pointer to the aligned storage.
*/
T const *ptr() const noexcept {
return reinterpret_cast<const T *>(storage_.data());
}
/**
* \brief Returns a reference to the aligned storage.
* \return A reference to the aligned storage.
*/
T &ref() noexcept {
return *ptr();
}
/**
* \brief Returns a reference to the aligned storage.
* \return A reference to the aligned storage.
*/
T const &ref() const noexcept {
return *ptr();
}
};
/**
* \brief Rounds up the given value to the given alignment.
* \tparam T The type of the value.
* \param value The value to be rounded up.
* \param alignment The alignment to be rounded up to.
* \return The rounded up value.
* \see https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number
*/
template <typename T>
constexpr T round_up(T value, T alignment) noexcept {
return (value + alignment - 1) & ~(alignment - 1);
}
} // namespace ipc

178
include/libipc/imp/byte.h Normal file
View File

@ -0,0 +1,178 @@
/**
* \file libipc/byte.h
* \author mutouyun (orz@orzz.org)
* \brief Define the byte type.
*/
#pragma once
#include <type_traits>
#include <cstdint>
#include <cstddef> // std::byte (since C++17)
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/span.h"
#include "libipc/imp/fmt.h"
#if defined(LIBIPC_CPP_17) && defined(__cpp_lib_byte)
#define LIBIPC_CPP_LIB_BYTE_
#endif // __cpp_lib_byte
namespace ipc {
class byte;
namespace detail_byte {
template <typename T>
using is_integral =
typename std::enable_if<std::is_integral<T>::value>::type;
template <typename T>
using is_not_byte =
typename std::enable_if<!std::is_same<
typename std::remove_cv<T>::type, byte>::value>::type;
} // namespace detail_byte
/**
* \brief A distinct type that implements the concept of byte as specified in the C++ language definition.
* \see https://en.cppreference.com/w/cpp/types/byte
*/
class byte {
std::uint8_t bits_;
public:
byte() noexcept = default;
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte(T v) noexcept
: bits_(static_cast<std::uint8_t>(v)) {}
#ifdef LIBIPC_CPP_LIB_BYTE_
constexpr byte(std::byte b) noexcept
: byte(std::to_integer<std::uint8_t>(b)) {}
#endif // LIBIPC_CPP_LIB_BYTE_
template <typename T, typename = detail_byte::is_integral<T>>
constexpr operator T() const noexcept {
return static_cast<T>(bits_);
}
#ifdef LIBIPC_CPP_LIB_BYTE_
constexpr operator std::byte() const noexcept {
/// \brief C++17 relaxed enum class initialization rules.
/// \see https://en.cppreference.com/w/cpp/language/enum#enum_relaxed_init_cpp17
return std::byte{bits_};
}
#endif // LIBIPC_CPP_LIB_BYTE_
friend bool operator==(byte const &lhs, byte const &rhs) noexcept {
return lhs.bits_ == rhs.bits_;
}
friend bool operator!=(byte const &lhs, byte const &rhs) noexcept {
return !(lhs == rhs);
}
};
/**
* \brief Non-member functions.
*/
template <typename T, typename = detail_byte::is_integral<T>>
constexpr T to_integer(byte b) noexcept {
return T(b);
}
/// \brief std::operator<<, operator>>
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte operator<<(byte b, T shift) noexcept {
return byte(to_integer<unsigned>(b) << shift);
}
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte operator>>(byte b, T shift) noexcept {
return byte(to_integer<unsigned>(b) >> shift);
}
/// \brief std::operator<<=, operator>>=
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte &operator<<=(byte &b, T shift) noexcept {
return b = b << shift;
}
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte &operator>>=(byte &b, T shift) noexcept {
return b = b >> shift;
}
/// \brief std::operator|, operator&, operator^, operator~
constexpr byte operator|(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) | to_integer<unsigned>(r)); }
constexpr byte operator&(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) & to_integer<unsigned>(r)); }
constexpr byte operator^(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) ^ to_integer<unsigned>(r)); }
constexpr byte operator~(byte b) noexcept { return byte(~to_integer<unsigned>(b)); }
/// \brief std::operator|=, operator&=, operator^=
constexpr byte &operator|=(byte &l, byte r) noexcept { return l = l | r; }
constexpr byte &operator&=(byte &l, byte r) noexcept { return l = l & r; }
constexpr byte &operator^=(byte &l, byte r) noexcept { return l = l ^ r; }
/// \brief Cast pointer to byte*.
template <typename T, typename = detail_byte::is_not_byte<T>>
byte *byte_cast(T *p) noexcept {
return reinterpret_cast<byte *>(p);
}
template <typename T, typename = detail_byte::is_not_byte<T>>
byte const *byte_cast(T const *p) noexcept {
return reinterpret_cast<byte const *>(p);
}
/// \brief Cast byte* to a pointer of another type.
template <typename T, typename = detail_byte::is_not_byte<T>>
T *byte_cast(byte *p) noexcept {
if (reinterpret_cast<std::size_t>(p) % alignof(T) != 0) {
return nullptr;
}
return reinterpret_cast<T *>(p);
}
template <typename T, typename U = typename std::add_const<T>::type,
typename = detail_byte::is_not_byte<T>>
U *byte_cast(byte const *p) noexcept {
if (reinterpret_cast<std::size_t>(p) % alignof(T) != 0) {
return nullptr;
}
return reinterpret_cast<U *>(p);
}
/// \brief Converts a span into a view of its underlying bytes.
/// \see https://en.cppreference.com/w/cpp/container/span/as_bytes
template <typename T,
typename Byte = typename std::conditional<std::is_const<T>::value, byte const, byte>::type>
auto as_bytes(span<T> s) noexcept -> span<Byte> {
return {byte_cast(s.data()), s.size_bytes()};
}
/// \brief Custom defined fmt_to method for imp::fmt
namespace detail_tag_invoke {
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, ipc::byte b) {
return ipc::to_string(ctx, static_cast<std::uint8_t>(b), "02x");
}
template <typename T,
typename = std::enable_if_t<std::is_same<std::decay_t<T>, ipc::byte>::value>>
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, fmt_ref<T> arg) noexcept {
return ipc::to_string(ctx, static_cast<std::uint8_t>(arg.param), arg.fstr);
}
} // namespace detail_tag_invoke
} // namespace ipc

View File

@ -0,0 +1,36 @@
/**
* \file libipc/codecvt.h
* \author mutouyun (orz@orzz.org)
* \brief Character set conversion interface.
*/
#pragma once
#include <string>
#include <cstddef>
#include "libipc/imp/export.h"
namespace ipc {
/**
* \brief The transform between UTF-8/16/32
*
* \param des The target string pointer can be nullptr
* \param dlen The target string length can be 0
*/
template <typename CharT, typename CharU>
LIBIPC_EXPORT std::size_t cvt_cstr(CharT const *src, std::size_t slen, CharU *des, std::size_t dlen) noexcept;
template <typename CharT, typename TraitsT, typename AllocT,
typename CharU, typename TraitsU, typename AllocU>
void cvt_sstr(std::basic_string<CharT, TraitsT, AllocT> const &src, std::basic_string<CharU, TraitsU, AllocU> &des) {
std::size_t dlen = cvt_cstr(src.c_str(), src.size(), (CharU *)nullptr, 0);
if (dlen == 0) {
des.clear();
return;
}
des.resize(dlen);
cvt_cstr(src.c_str(), src.size(), &des[0], des.size());
}
} // namespace ipc

View File

@ -0,0 +1,226 @@
/**
* \file libipc/detect_plat.h
* \author mutouyun (orz@orzz.org)
* \brief Define platform detection related interfaces.
*/
#pragma once
/// \brief OS check.
#if defined(WINCE) || defined(_WIN32_WCE)
# define LIBIPC_OS_WINCE
#elif defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
(defined(__x86_64) && defined(__MSYS__))
#define LIBIPC_OS_WIN64
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
defined(__NT__) || defined(__MSYS__)
# define LIBIPC_OS_WIN32
#elif defined(__QNX__) || defined(__QNXNTO__)
# define LIBIPC_OS_QNX
#elif defined(__APPLE__)
# define LIBIPC_OS_APPLE
#elif defined(ANDROID) || defined(__ANDROID__)
# define LIBIPC_OS_ANDROID
#elif defined(__linux__) || defined(__linux)
# define LIBIPC_OS_LINUX
#elif defined(_POSIX_VERSION)
# define LIBIPC_OS_POSIX
#else
# error "This OS is unsupported."
#endif
#if defined(LIBIPC_OS_WIN32) || defined(LIBIPC_OS_WIN64) || \
defined(LIBIPC_OS_WINCE)
# define LIBIPC_OS_WIN
#endif
/// \brief Compiler check.
#if defined(_MSC_VER)
# define LIBIPC_CC_MSVC _MSC_VER
# define LIBIPC_CC_MSVC_2015 1900
# define LIBIPC_CC_MSVC_2017 1910
# define LIBIPC_CC_MSVC_2019 1920
# define LIBIPC_CC_MSVC_2022 1930
#elif defined(__GNUC__)
# define LIBIPC_CC_GNUC __GNUC__
# if defined(__clang__)
# define LIBIPC_CC_CLANG
#endif
#else
# error "This compiler is unsupported."
#endif
/// \brief Instruction set.
/// \see https://sourceforge.net/p/predef/wiki/Architectures/
#if defined(_M_X64) || defined(_M_AMD64) || \
defined(__x86_64__) || defined(__x86_64) || \
defined(__amd64__) || defined(__amd64)
# define LIBIPC_INSTR_X64
#elif defined(_M_IA64) || defined(__IA64__) || defined(_IA64) || \
defined(__ia64__) || defined(__ia64)
# define LIBIPC_INSTR_I64
#elif defined(_M_IX86) || defined(_X86_) || defined(__i386__) || defined(__i386)
# define LIBIPC_INSTR_X86
#elif defined(_M_ARM64) || defined(__arm64__) || defined(__aarch64__)
# define LIBIPC_INSTR_ARM64
#elif defined(_M_ARM) || defined(_ARM) || defined(__arm__) || defined(__arm)
# define LIBIPC_INSTR_ARM32
#else
# error "This instruction set is unsupported."
#endif
#if defined(LIBIPC_INSTR_X86) || defined(LIBIPC_INSTR_X64)
# define LIBIPC_INSTR_X86_64
#elif defined(LIBIPC_INSTR_ARM32) || defined(LIBIPC_INSTR_ARM64)
# define LIBIPC_INSTR_ARM
#endif
/// \brief Byte order.
#if defined(__BYTE_ORDER__)
# define LIBIPC_ENDIAN_BIG (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
# define LIBIPC_ENDIAN_LIT (!LIBIPC_ENDIAN_BIG)
#else
# define LIBIPC_ENDIAN_BIG (0)
# define LIBIPC_ENDIAN_LIT (1)
#endif
/// \brief C++ version.
#if (__cplusplus >= 202002L) && !defined(LIBIPC_CPP_20)
# define LIBIPC_CPP_20
#endif
#if (__cplusplus >= 201703L) && !defined(LIBIPC_CPP_17)
# define LIBIPC_CPP_17
#endif
#if /*(__cplusplus >= 201402L) &&*/ !defined(LIBIPC_CPP_14)
# define LIBIPC_CPP_14
#endif
#if !defined(LIBIPC_CPP_20) && \
!defined(LIBIPC_CPP_17) && \
!defined(LIBIPC_CPP_14)
# error "This C++ version is unsupported."
#endif
/// \brief Feature cross-platform adaptation.
#if defined(LIBIPC_CPP_17)
# define LIBIPC_INLINE_CONSTEXPR inline constexpr
#else
# define LIBIPC_INLINE_CONSTEXPR constexpr
#endif
/// \brief C++ attributes.
/// \see https://en.cppreference.com/w/cpp/language/attributes
#if defined(__has_cpp_attribute)
# if __has_cpp_attribute(fallthrough)
# define LIBIPC_FALLTHROUGH [[fallthrough]]
# endif
# if __has_cpp_attribute(maybe_unused)
# define LIBIPC_UNUSED [[maybe_unused]]
# endif
# if __has_cpp_attribute(likely)
# define LIBIPC_LIKELY(...) (__VA_ARGS__) [[likely]]
# endif
# if __has_cpp_attribute(unlikely)
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__) [[unlikely]]
# endif
# if __has_cpp_attribute(nodiscard)
# define LIBIPC_NODISCARD [[nodiscard]]
# endif
# if __has_cpp_attribute(assume)
# define LIBIPC_ASSUME(...) [[assume(__VA_ARGS__)]]
# endif
#endif
#if !defined(LIBIPC_FALLTHROUGH)
# if defined(LIBIPC_CC_GNUC)
# define LIBIPC_FALLTHROUGH __attribute__((__fallthrough__))
# else
# define LIBIPC_FALLTHROUGH
# endif
#endif
#if !defined(LIBIPC_UNUSED)
# if defined(LIBIPC_CC_GNUC)
# define LIBIPC_UNUSED __attribute__((__unused__))
# elif defined(LIBIPC_CC_MSVC)
# define LIBIPC_UNUSED __pragma(warning(suppress: 4100 4101 4189))
# else
# define LIBIPC_UNUSED
# endif
#endif
#if !defined(LIBIPC_LIKELY)
# if defined(__has_builtin)
# if __has_builtin(__builtin_expect)
# define LIBIPC_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1))
# endif
# endif
#endif
#if !defined(LIBIPC_LIKELY)
# define LIBIPC_LIKELY(...) (__VA_ARGS__)
#endif
#if !defined(LIBIPC_UNLIKELY)
# if defined(__has_builtin)
# if __has_builtin(__builtin_expect)
# define LIBIPC_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0))
# endif
# endif
#endif
#if !defined(LIBIPC_UNLIKELY)
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__)
#endif
#if !defined(LIBIPC_NODISCARD)
/// \see https://stackoverflow.com/questions/4226308/msvc-equivalent-of-attribute-warn-unused-result
# if defined(LIBIPC_CC_GNUC) && (LIBIPC_CC_GNUC >= 4)
# define LIBIPC_NODISCARD __attribute__((warn_unused_result))
# elif defined(LIBIPC_CC_MSVC) && (LIBIPC_CC_MSVC >= 1700)
# define LIBIPC_NODISCARD _Check_return_
# else
# define LIBIPC_NODISCARD
# endif
#endif
#if !defined(LIBIPC_ASSUME)
# if defined(__has_builtin)
# if __has_builtin(__builtin_assume)
/// \see https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-assume
# define LIBIPC_ASSUME(...) __builtin_assume(__VA_ARGS__)
# elif __has_builtin(__builtin_unreachable)
/// \see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005funreachable
# define LIBIPC_ASSUME(...) do { if (!(__VA_ARGS__)) __builtin_unreachable(); } while (false)
# endif
# endif
#endif
#if !defined(LIBIPC_ASSUME)
# if defined(LIBIPC_CC_MSVC)
/// \see https://learn.microsoft.com/en-us/cpp/intrinsics/assume?view=msvc-140
# define LIBIPC_ASSUME(...) __assume(__VA_ARGS__)
# else
# define LIBIPC_ASSUME(...)
# endif
#endif
/// \see https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_exceptions.html
/// https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros
/// https://stackoverflow.com/questions/6487013/programmatically-determine-whether-exceptions-are-enabled
#if defined(__cpp_exceptions) && __cpp_exceptions || \
defined(__EXCEPTIONS) || defined(_CPPUNWIND)
# define LIBIPC_TRY try
# define LIBIPC_CATCH(...) catch (__VA_ARGS__)
# define LIBIPC_THROW($EXCEPTION, ...) throw $EXCEPTION
#else
# define LIBIPC_TRY if (true)
# define LIBIPC_CATCH(...) else if (false)
# define LIBIPC_THROW($EXCEPTION, ...) return __VA_ARGS__
#endif

View File

@ -0,0 +1,27 @@
/**
* \file libipc/error.h
* \author mutouyun (orz@orzz.org)
* \brief A platform-dependent error code.
*/
#pragma once
#include <system_error>
#include <string>
#include <cstdint>
#include "libipc/imp/export.h"
#include "libipc/imp/fmt_cpo.h"
namespace ipc {
/**
* \brief Custom defined fmt_to method for imp::fmt
*/
namespace detail_tag_invoke {
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, std::error_code const &ec) noexcept {
return fmt_to(ctx, '[', ec.value(), ": ", ec.message(), ']');
}
} // namespace detail_tag_invoke
} // namespace ipc

View File

@ -0,0 +1,392 @@
/**
* \file libipc/expected.h
* \author mutouyun (orz@orzz.org)
* \brief Provides a way to store either of two values.
*/
#pragma once
#include <type_traits>
#include <utility> // std::exchange
#include <array>
#include <memory> // std::addressof
#include <cstddef> // std::nullptr_t
#include "libipc/imp/uninitialized.h"
#include "libipc/imp/generic.h"
#include "libipc/imp/byte.h"
namespace ipc {
/**
* \brief In-place construction tag for unexpected value in expected.
* \see https://en.cppreference.com/w/cpp/utility/expected/unexpect_t
*/
struct unexpected_t {
explicit unexpected_t() = default;
};
constexpr unexpected_t unexpected{};
/**
* \class template <typename T, typename E> expected
* \brief Provides a way to store either of two values.
* \see https://en.cppreference.com/w/cpp/utility/expected
* \tparam T - the type of the expected value.
* \tparam E - the type of the unexpected value.
*/
template <typename T, typename E>
class expected;
namespace detail_expected {
template <typename T, typename E>
struct data_union {
using const_value_t = typename std::add_const<T>::type;
using const_error_t = typename std::add_const<E>::type;
union {
T value_; ///< the expected value
E error_; ///< the unexpected value
};
data_union(data_union const &) = delete;
data_union &operator=(data_union const &) = delete;
data_union(std::nullptr_t) noexcept {}
~data_union() {}
template <typename... A>
data_union(in_place_t, A &&...args) : value_{std::forward<A>(args)...} {}
template <typename... A>
data_union(unexpected_t, A &&...args) : error_{std::forward<A>(args)...} {}
void destruct_value() noexcept { destroy(&value_); }
void destruct_error() noexcept { destroy(&error_); }
const_value_t & value() const & noexcept { return value_; }
T & value() & noexcept { return value_; }
const_value_t &&value() const && noexcept { return std::move(value_); }
T && value() && noexcept { return std::move(value_); }
const_error_t & error() const & noexcept { return error_; }
E & error() & noexcept { return error_; }
const_error_t &&error() const && noexcept { return std::move(error_); }
E && error() && noexcept { return std::move(error_); }
};
template <typename E>
struct data_union<void, E> {
using const_error_t = typename std::add_const<E>::type;
alignas(E) std::array<byte, sizeof(E)> error_; ///< the unexpected value
data_union(data_union const &) = delete;
data_union &operator=(data_union const &) = delete;
data_union(std::nullptr_t) noexcept {}
template <typename... A>
data_union(in_place_t, A &&...) noexcept {}
template <typename... A>
data_union(unexpected_t, A &&...args) {
construct<E>(&error_, std::forward<A>(args)...);
}
void destruct_value() noexcept {}
void destruct_error() noexcept { destroy(reinterpret_cast<E *>(&error_)); }
const_error_t & error() const & noexcept { return *reinterpret_cast<E *>(error_.data()); }
E & error() & noexcept { return *reinterpret_cast<E *>(error_.data()); }
const_error_t &&error() const && noexcept { return std::move(*reinterpret_cast<E *>(error_.data())); }
E && error() && noexcept { return std::move(*reinterpret_cast<E *>(error_.data())); }
};
template <typename T, typename E>
auto destruct(bool /*has_value*/, data_union<T, E> &/*data*/) noexcept
-> typename std::enable_if<std::is_trivially_destructible<T>::value &&
std::is_trivially_destructible<E>::value>::type {
// Do nothing.
}
template <typename T, typename E>
auto destruct(bool has_value, data_union<T, E> &data) noexcept
-> typename std::enable_if<!std::is_trivially_destructible<T>::value &&
std::is_trivially_destructible<E>::value>::type {
if (has_value) data.destruct_value();
}
template <typename T, typename E>
auto destruct(bool has_value, data_union<T, E> &data) noexcept
-> typename std::enable_if< std::is_trivially_destructible<T>::value &&
!std::is_trivially_destructible<E>::value>::type {
if (!has_value) data.destruct_error();
}
template <typename T, typename E>
auto destruct(bool has_value, data_union<T, E> &data) noexcept
-> typename std::enable_if<!std::is_trivially_destructible<T>::value &&
!std::is_trivially_destructible<E>::value>::type {
if (has_value) {
data.destruct_value();
} else {
data.destruct_error();
}
}
template <typename S, typename T, typename E>
struct value_getter : data_union<T, E> {
using data_union<T, E>::data_union;
template <typename U>
value_getter(U &&other) : data_union<T, E>(nullptr) {
if (other) {
construct<data_union<T, E>>(this, in_place, std::forward<U>(other).value());
} else {
construct<data_union<T, E>>(this, unexpected, std::forward<U>(other).error());
}
}
T const *operator->() const noexcept { return std::addressof(this->value()); }
T * operator->() noexcept { return std::addressof(this->value()); }
T const & operator*() const & noexcept { return this->value(); }
T & operator*() & noexcept { return this->value(); }
T const &&operator*() const && noexcept { return std::move(this->value()); }
T && operator*() && noexcept { return std::move(this->value()); }
template <typename U>
T value_or(U &&def) const & {
return bool(*static_cast<S *>(this)) ? **this : static_cast<T>(std::forward<U>(def));
}
template <typename U>
T value_or(U &&def) && {
return bool(*static_cast<S *>(this)) ? std::move(**this) : static_cast<T>(std::forward<U>(def));
}
template <typename... A>
T &emplace(A &&...args) {
static_cast<S *>(this)->reconstruct(in_place, std::forward<A>(args)...);
return this->value();
}
void swap(S &other) {
if (bool(*static_cast<S *>(this)) && bool(other)) {
std::swap(this->value(), other.value());
} else if (!*static_cast<S *>(this) && !other) {
std::swap(this->error(), other.error());
} else if (!*static_cast<S *>(this) && bool(other)) {
E err(std::move(this->error()));
this->emplace(std::move(other.value()));
other.reconstruct(unexpected, std::move(err));
} else /*if (bool(*this) && !other)*/ {
E err(std::move(other.error()));
other.emplace(std::move(this->value()));
static_cast<S *>(this)->reconstruct(unexpected, std::move(err));
}
}
};
template <typename S, typename E>
struct value_getter<S, void, E> : data_union<void, E> {
using data_union<void, E>::data_union;
template <typename U>
value_getter(U &&other) : data_union<void, E>(nullptr) {
if (other) {
construct<data_union<void, E>>(this, in_place);
} else {
construct<data_union<void, E>>(this, unexpected, std::forward<U>(other).error());
}
}
void emplace() noexcept {
static_cast<S *>(this)->reconstruct(in_place);
}
void swap(S &other) {
if (bool(*static_cast<S *>(this)) && bool(other)) {
return;
} else if (!*static_cast<S *>(this) && !other) {
std::swap(this->error(), other.error());
} else if (!*static_cast<S *>(this) && bool(other)) {
E err(std::move(this->error()));
this->emplace();
other.reconstruct(unexpected, std::move(err));
} else /*if (bool(*this) && !other)*/ {
E err(std::move(other.error()));
other.emplace();
static_cast<S *>(this)->reconstruct(unexpected, std::move(err));
}
}
};
/**
* \brief Define the expected storage.
*/
template <typename T, typename E>
struct storage : value_getter<storage<T, E>, T, E> {
using getter_t = value_getter<storage<T, E>, T, E>;
bool has_value_;
template <typename... A>
storage(in_place_t, A &&...args)
: getter_t(in_place, std::forward<A>(args)...)
, has_value_(true) {}
template <typename... A>
storage(unexpected_t, A &&...args)
: getter_t(unexpected, std::forward<A>(args)...)
, has_value_(false) {}
storage(storage const &other)
: getter_t(other)
, has_value_(other.has_value_) {}
storage(storage &&other)
: getter_t(std::move(other))
/// After construction, has_value() is equal to other.has_value().
, has_value_(other.has_value_) {}
template <typename T_, typename E_>
storage(storage<T_, E_> const &other)
: getter_t(other)
, has_value_(other.has_value_) {}
template <typename T_, typename E_>
storage(storage<T_, E_> &&other)
: getter_t(std::move(other))
/// After construction, has_value() is equal to other.has_value().
, has_value_(other.has_value_) {}
bool has_value() const noexcept {
return has_value_;
}
explicit operator bool() const noexcept {
return this->has_value();
}
protected:
friend getter_t;
template <typename... A>
void reconstruct(A &&...args) {
destroy(this);
construct<storage>(this, std::forward<A>(args)...);
}
};
/// \brief The invoke forwarding helper.
template <typename F, typename... A>
auto invoke(F &&f, A &&...args) noexcept(
noexcept(std::forward<F>(f)(std::forward<A>(args)...)))
-> decltype(std::forward<F>(f)(std::forward<A>(args)...)) {
return std::forward<F>(f)(std::forward<A>(args)...);
}
template <typename F, typename... A>
auto invoke(F &&f, A &&...args) noexcept(
noexcept(std::forward<F>(f)()))
-> decltype(std::forward<F>(f)()) {
return std::forward<F>(f)();
}
/// \brief and_then helper.
template <typename E, typename F,
typename R = decltype(invoke(std::declval<F>(), *std::declval<E>()))>
R and_then(E &&exp, F &&f) {
static_assert(is_specialized<expected, R>::value, "F must return an `expected`.");
return bool(exp) ? invoke(std::forward<F>(f), *std::forward<E>(exp))
: R(unexpected, std::forward<E>(exp).error());
}
/// \brief or_else helper.
template <typename E, typename F,
typename R = decltype(invoke(std::declval<F>(), std::declval<E>().error()))>
R or_else(E &&exp, F &&f) {
static_assert(is_specialized<expected, R>::value, "F must return an `expected`.");
return bool(exp) ? std::forward<E>(exp)
: invoke(std::forward<F>(f), std::forward<E>(exp).error());
}
} // namespace detail_expected
/**
* \class template <typename T, typename E> expected
* \brief Provides a way to store either of two values.
*/
template <typename T, typename E>
class expected : public detail_expected::storage<typename std::remove_cv<T>::type, E> {
public:
using value_type = typename std::remove_cv<T>::type;
using error_type = E;
using detail_expected::storage<value_type, E>::storage;
expected(expected const &) = default;
expected(expected &&) = default;
expected()
: detail_expected::storage<value_type, E>(in_place) {}
expected &operator=(expected other) {
this->swap(other);
return *this;
}
// Monadic operations
template <typename F>
auto and_then(F &&f) & {
return detail_expected::and_then(*this, std::forward<F>(f));
}
template <typename F>
auto and_then(F &&f) const & {
return detail_expected::and_then(*this, std::forward<F>(f));
}
template <typename F>
auto and_then(F &&f) && {
return detail_expected::and_then(std::move(*this), std::forward<F>(f));
}
template <typename F>
auto or_else(F &&f) & {
return detail_expected::or_else(*this, std::forward<F>(f));
}
template <typename F>
auto or_else(F &&f) const & {
return detail_expected::or_else(*this, std::forward<F>(f));
}
template <typename F>
auto or_else(F &&f) && {
return detail_expected::or_else(std::move(*this), std::forward<F>(f));
}
};
// Compares
template <typename T1, typename E1, typename T2, typename E2>
bool operator==(expected<T1, E1> const &lhs, expected<T2, E2> const &rhs) {
return (lhs.has_value() == rhs.has_value())
&& (lhs.has_value() ? *lhs == *rhs : lhs.error() == rhs.error());
}
template <typename E1, typename E2>
bool operator==(expected<void, E1> const &lhs, expected<void, E2> const &rhs) {
return (lhs.has_value() == rhs.has_value())
&& (lhs.has_value() || lhs.error() == rhs.error());
}
template <typename T1, typename E1, typename T2, typename E2>
bool operator!=(expected<T1, E1> const &lhs, expected<T2, E2> const &rhs) {
return !(lhs == rhs);
}
} // namespace ipc

45
include/libipc/imp/export.h Executable file
View File

@ -0,0 +1,45 @@
/**
* \file libipc/export.h
* \author mutouyun (orz@orzz.org)
* \brief Define the symbol export interfaces.
*/
#pragma once
#include "libipc/imp/detect_plat.h"
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
# define LIBIPC_DECL_EXPORT Q_DECL_EXPORT
# define LIBIPC_DECL_IMPORT Q_DECL_IMPORT
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
/**
* \brief Compiler & system detection for LIBIPC_DECL_EXPORT & LIBIPC_DECL_IMPORT.
* Not using QtCore cause it shouldn't depend on Qt.
*/
# if defined(LIBIPC_CC_MSVC) || defined(LIBIPC_OS_WIN)
# define LIBIPC_DECL_EXPORT __declspec(dllexport)
# define LIBIPC_DECL_IMPORT __declspec(dllimport)
# elif defined(LIBIPC_OS_ANDROID) || defined(LIBIPC_OS_LINUX) || defined(LIBIPC_CC_GNUC)
# define LIBIPC_DECL_EXPORT __attribute__((visibility("default")))
# define LIBIPC_DECL_IMPORT __attribute__((visibility("default")))
# else
# define LIBIPC_DECL_EXPORT __attribute__((visibility("default")))
# define LIBIPC_DECL_IMPORT __attribute__((visibility("default")))
# endif
#endif // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
/**
* \brief Define LIBIPC_EXPORT for exporting function & class.
*/
#ifndef LIBIPC_EXPORT
# if defined(LIBIPC_LIBRARY_SHARED_BUILDING__)
# define LIBIPC_EXPORT LIBIPC_DECL_EXPORT
# elif defined(LIBIPC_LIBRARY_SHARED_USING__)
# define LIBIPC_EXPORT LIBIPC_DECL_IMPORT
# else
# define LIBIPC_EXPORT
# endif
#endif /*LIBIPC_EXPORT*/

177
include/libipc/imp/fmt.h Normal file
View File

@ -0,0 +1,177 @@
/**
* \file libipc/fmt.h
* \author mutouyun (orz@orzz.org)
* \brief String formatting.
*
* \remarks The current performance is not high,
* because I use std::sprintf directly for formatting for convenience.
*/
#pragma once
#include <string>
#include <utility>
#include <type_traits>
#include <chrono> // std::chrono::time_point
#include <tuple>
#include <cstddef>
#include <ctime> // std::tm, std::localtime
#include "libipc/imp/fmt_cpo.h"
#include "libipc/imp/span.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/export.h"
namespace ipc {
/**
* \brief The format string reference wrapper.
*/
template <typename T>
struct fmt_ref {
span<char const> fstr;
T param;
};
/**
* \brief Conversion specifiers.
*
* \remarks Just like printf, the format string is of the form
* [flags][field_width][.precision][conversion_character]
*
* \see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html
*/
template <std::size_t N>
auto spec(char const (&fstr)[N]) noexcept {
return [&fstr](auto &&arg) noexcept {
using arg_t = decltype(arg);
return fmt_ref<arg_t> {{fstr}, static_cast<arg_t>(arg)};
};
}
/**
* \brief String formatting function.
*
* \param args arguments that support the fmt output
* \return an empty string if the fmt output fails
*/
template <typename... A>
LIBIPC_NODISCARD std::string fmt(A &&...args) {
std::string joined;
fmt_context ctx(joined);
if (fmt_to(ctx, std::forward<A>(args)...)) {
return ctx.finish() ? joined : "";
}
return {};
}
/// \brief String types.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char const * a) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::string const &a) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char const * a, span<char const> fstr) noexcept;
inline bool to_string(fmt_context &ctx, std::string const &a, span<char const> fstr) noexcept { return to_string(ctx, a.c_str(), fstr); }
/// \brief Character to string conversion.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char a) noexcept;
#if defined(LIBIPC_CPP_20)
inline bool to_string(fmt_context &ctx, char8_t a) noexcept { return to_string(ctx, (char)a); }
#endif // defined(LIBIPC_CPP_20)
LIBIPC_EXPORT bool to_string(fmt_context &ctx, wchar_t a) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char16_t a) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char32_t a) noexcept;
/// \brief Conversion of numeric types to strings.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed short a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned short a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed int a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned int a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed long a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned long a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed long long a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned long long a, span<char const> fstr = {}) noexcept;
inline bool to_string(fmt_context &ctx, signed char a, span<char const> fstr = {}) noexcept { return to_string(ctx, (int)a, fstr); }
inline bool to_string(fmt_context &ctx, unsigned char a, span<char const> fstr = {}) noexcept { return to_string(ctx, (unsigned)a, fstr); }
/// \brief Conversion of floating point type to strings.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, double a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, long double a, span<char const> fstr = {}) noexcept;
inline bool to_string(fmt_context &ctx, float a, span<char const> fstr = {}) noexcept { return to_string(ctx, (double)a, fstr); }
/// \brief Pointer.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::nullptr_t) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, void const volatile *a) noexcept;
template <typename T,
typename = std::enable_if_t<!std::is_same<T, char>::value>>
inline bool to_string(fmt_context &ctx, T const volatile *a) noexcept { return to_string(ctx, (void *)a); }
/// \brief Date and time.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::tm const &a, span<char const> fstr = {}) noexcept;
namespace detail_fmt {
/**
* \brief Convert std::time_t to std::string.
* \return an empty string if the conversion fails
*/
inline bool time_to_string(fmt_context &ctx, std::time_t tt, span<char const> fstr) noexcept {
#if defined(LIBIPC_CC_MSVC)
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s
std::tm tm{};
if (::localtime_s(&tm, &tt) != 0) {
return {};
}
return to_string(ctx, tm, fstr);
#else
return to_string(ctx, *std::localtime(&tt), fstr);
#endif
}
} // namespace detail_fmt
template <class Clock, class Duration>
bool to_string(fmt_context &ctx, std::chrono::time_point<Clock, Duration> const &a, span<char const> fstr = {}) noexcept {
return detail_fmt::time_to_string(ctx, std::chrono::system_clock::to_time_t(a), fstr);
}
/**
* \brief Predefined fmt_to method
*/
namespace detail_tag_invoke {
template <typename T>
auto tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, T &&arg) noexcept
-> decltype(ipc::to_string(ctx, std::forward<T>(arg))) {
return ipc::to_string(ctx, std::forward<T>(arg));
}
template <typename T>
auto tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, fmt_ref<T> arg) noexcept
-> decltype(ipc::to_string(ctx, static_cast<T>(arg.param), arg.fstr)) {
return ipc::to_string(ctx, static_cast<T>(arg.param), arg.fstr);
}
template <typename T>
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, span<T> s) {
if (s.empty()) {
return false;
}
if (!fmt_to(ctx, s[0])) {
return false;
}
for (std::size_t i = 1; i < s.size(); ++i) {
if (!fmt_to(ctx, ' ', s[i])) return false;
}
return true;
}
template <typename Tp, std::size_t... I>
bool unfold_tuple_fmt_to(fmt_context &ctx, Tp const &tp, std::index_sequence<I...>) {
return fmt_to(ctx, std::get<I>(tp)...);
}
template <typename... T>
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, std::tuple<T...> const &tp) {
return unfold_tuple_fmt_to(ctx, tp, std::index_sequence_for<T...>{});
}
} // namespace detail_tag_invoke
} // namespace ipc

View File

@ -0,0 +1,66 @@
/**
* \file libipc/fmt_cpo.h
* \author mutouyun (orz@orzz.org)
* \brief String formatting CPO.
*/
#pragma once
#include <string>
#include <cstddef>
#include <array>
#include "libipc/imp/generic.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/span.h"
#include "libipc/imp/export.h"
namespace ipc {
/**
* \class class LIBIPC_EXPORT fmt_context
* \brief The context of fmt.
*/
class LIBIPC_EXPORT fmt_context {
std::array<char, 2048U> sbuf_; ///< stack buffer
std::string &joined_;
std::size_t offset_;
public:
fmt_context(std::string &j) noexcept;
std::size_t capacity() noexcept;
void reset() noexcept;
bool finish() noexcept;
span<char> buffer(std::size_t sz) noexcept;
void expend(std::size_t sz) noexcept;
bool append(span<char const> const &str) noexcept;
};
/// \brief Supports custom fmt_to methods for ipc::fmt.
namespace detail_tag_invoke {
class fmt_to_t {
template <typename A1>
bool get_result(fmt_context &ctx, A1 && a1) const {
return ipc::tag_invoke(fmt_to_t{}, ctx, std::forward<A1>(a1));
}
template <typename A1, typename... A>
bool get_result(fmt_context &ctx, A1 && a1, A &&...args) const {
return get_result(ctx, std::forward<A1>(a1))
&& get_result(ctx, std::forward<A>(args)...);
}
public:
template <typename... A>
bool operator()(fmt_context &ctx, A &&...args) const {
return get_result(ctx, std::forward<A>(args)...);
}
};
} // namespace detail_tag_invoke
constexpr detail_tag_invoke::fmt_to_t fmt_to{};
} // namespace ipc

View File

@ -0,0 +1,301 @@
/**
* \file libipc/generic.h
* \author mutouyun (orz@orzz.org)
* \brief Tools for generic programming.
*/
#pragma once
#include <utility>
#include <type_traits> // std::declval, std::true_type, std::false_type
#include <cstddef> // std::size_t
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \brief Utility metafunction that maps a sequence of any types to the type void
* \see https://en.cppreference.com/w/cpp/types/void_t
*/
template <typename...>
using void_t = void;
/**
* \brief A type-list for generic programming.
*/
template <typename...>
struct types {};
/**
* \brief To indicate that the contained object should be constructed in-place.
* \see https://en.cppreference.com/w/cpp/utility/in_place
*/
#if defined(LIBIPC_CPP_17)
using std::in_place_t;
using std::in_place;
#else /*!LIBIPC_CPP_17*/
struct in_place_t {
explicit in_place_t() = default;
};
constexpr in_place_t in_place{};
#endif/*!LIBIPC_CPP_17*/
/**
* \brief A general pattern for supporting customisable functions
* \see https://www.open-std.org/jtc1/sc22/WG21/docs/papers/2019/p1895r0.pdf
*/
namespace detail_tag_invoke {
void tag_invoke();
struct tag_invoke_t {
template <typename T, typename... A>
constexpr auto operator()(T tag, A &&...args) const
noexcept(noexcept(tag_invoke(std::forward<T>(tag), std::forward<A>(args)...)))
-> decltype(tag_invoke(std::forward<T>(tag), std::forward<A>(args)...)) {
return tag_invoke(std::forward<T>(tag), std::forward<A>(args)...);
}
};
} // namespace detail_tag_invoke
constexpr detail_tag_invoke::tag_invoke_t tag_invoke{};
/**
* \brief Circumventing forwarding reference may override copy and move constructs.
* \see https://mpark.github.io/programming/2014/06/07/beware-of-perfect-forwarding-constructors/
*/
namespace detail_not_match {
template <typename T, typename... A>
struct is_same_first : std::false_type {};
template <typename T>
struct is_same_first<T, T> : std::true_type {};
} // namespace detail_not_match
template <typename T, typename... A>
using not_match =
typename std::enable_if<!detail_not_match::is_same_first<T,
typename std::decay<A>::type...>::value, bool>::type;
/**
* \brief Determines whether a type is specialized from a particular template.
*/
template <template <typename...> class Tt, typename T>
struct is_specialized : std::false_type {};
template <template <typename...> class Tt, typename... A>
struct is_specialized<Tt, Tt<A...>> : std::true_type {};
/**
* \brief Copy the cv qualifier and reference of the source type to the target type.
*/
template <typename Src, typename Des>
struct copy_cvref {
using type = Des;
};
template <typename Src, typename Des>
struct copy_cvref<Src const, Des> {
using type = typename std::add_const<Des>::type;
};
template <typename Src, typename Des>
struct copy_cvref<Src volatile, Des> {
using type = typename std::add_volatile<Des>::type;
};
template <typename Src, typename Des>
struct copy_cvref<Src const volatile, Des> {
using type = typename std::add_cv<Des>::type;
};
template <typename Src, typename Des>
struct copy_cvref<Src &, Des> {
using type = typename std::add_lvalue_reference<
typename copy_cvref<Src, Des>::type>::type;
};
template <typename Src, typename Des>
struct copy_cvref<Src &&, Des> {
using type = typename std::add_rvalue_reference<
typename copy_cvref<Src, Des>::type>::type;
};
template <typename Src, typename Des>
using copy_cvref_t = typename copy_cvref<Src, Des>::type;
/**
* \brief Returns the size of the given range.
* \see https://en.cppreference.com/w/cpp/iterator/size
*/
namespace detail_countof {
template <typename T>
struct trait_has_size {
private:
template <typename Type>
static std::true_type check(decltype(std::declval<Type>().size())*);
template <typename Type>
static std::false_type check(...);
public:
using type = decltype(check<T>(nullptr));
static constexpr auto value = type::value;
};
template <typename T>
struct trait_has_Size {
private:
template <typename Type>
static std::true_type check(decltype(std::declval<Type>().Size())*);
template <typename Type>
static std::false_type check(...);
public:
using type = decltype(check<T>(nullptr));
static constexpr auto value = type::value;
};
template <typename C, bool = trait_has_size<C>::value
, bool = trait_has_Size<C>::value>
struct trait;
template <typename T, std::size_t N>
struct trait<T[N], false, false> {
static constexpr auto countof(T const (&)[N]) noexcept {
return N;
}
};
template <typename C, bool B>
struct trait<C, true, B> {
static constexpr auto countof(C const &c) noexcept(noexcept(c.size())) {
return c.size();
}
};
template <typename C>
struct trait<C, false, true> {
static constexpr auto countof(C const &c) noexcept(noexcept(c.Size())) {
return c.Size();
}
};
} // namespace detail_countof
template <typename C,
typename T = detail_countof::trait<C>,
typename R = decltype(T::countof(std::declval<C const &>()))>
constexpr R countof(C const &c) noexcept(noexcept(T::countof(c))) {
return T::countof(c);
}
/**
* \brief Returns the data pointer of the given range.
* \see https://en.cppreference.com/w/cpp/iterator/data
*/
namespace detail_dataof {
template <typename T>
struct trait_has_data {
private:
template <typename Type>
static std::true_type check(decltype(std::declval<Type>().data())*);
template <typename Type>
static std::false_type check(...);
public:
using type = decltype(check<T>(nullptr));
static constexpr auto value = type::value;
};
template <typename T>
struct trait_has_Data {
private:
template <typename Type>
static std::true_type check(decltype(std::declval<Type>().Data())*);
template <typename Type>
static std::false_type check(...);
public:
using type = decltype(check<T>(nullptr));
static constexpr auto value = type::value;
};
template <typename C, bool = trait_has_data<C>::value
, bool = trait_has_Data<C>::value>
struct trait;
template <typename T, std::size_t N>
struct trait<T[N], false, false> {
static constexpr T const *dataof(T const (&arr)[N]) noexcept {
return arr;
}
static constexpr T *dataof(T (&arr)[N]) noexcept {
return arr;
}
};
template <typename C, bool B>
struct trait<C, true, B> {
template <typename T>
static constexpr auto dataof(T &&c) noexcept(noexcept(c.data())) {
return std::forward<T>(c).data();
}
};
template <typename C>
struct trait<C, false, true> {
template <typename T>
static constexpr auto dataof(T &&c) noexcept(noexcept(c.Data())) {
return std::forward<T>(c).Data();
}
};
template <typename C>
struct trait<C, false, false> {
template <typename T>
static constexpr T const *dataof(std::initializer_list<T> il) noexcept {
return il.begin();
}
template <typename T>
static constexpr T const *dataof(T const *p) noexcept {
return p;
}
};
} // namespace detail_dataof
template <typename C,
typename T = detail_dataof::trait<std::remove_cv_t<std::remove_reference_t<C>>>,
typename R = decltype(T::dataof(std::declval<C>()))>
constexpr R dataof(C &&c) noexcept(noexcept(T::dataof(std::forward<C>(c)))) {
return T::dataof(std::forward<C>(c));
}
/// \brief Returns after converting the value to the underlying type of E.
/// \see https://en.cppreference.com/w/cpp/types/underlying_type
/// https://en.cppreference.com/w/cpp/utility/to_underlying
template <typename E>
constexpr auto underlyof(E e) noexcept {
return static_cast<std::underlying_type_t<E>>(e);
}
namespace detail_horrible_cast {
template <typename T, typename U>
union temp {
U in;
T out;
};
} // namespace detail_horrible_cast
template <typename T, typename U>
constexpr auto horrible_cast(U &&in) noexcept
-> typename std::enable_if<std::is_trivially_copyable<T>::value
&& std::is_trivially_copyable<std::decay_t<U>>::value, T>::type {
return detail_horrible_cast::temp<T, std::decay_t<U>>{std::forward<U>(in)}.out;
}
} // namespace ipc

195
include/libipc/imp/log.h Normal file
View File

@ -0,0 +1,195 @@
/**
* \file libipc/log.h
* \author mutouyun (orz@orzz.org)
* \brief Simple log output component.
*/
#pragma once
#include <string>
#include <type_traits>
#include <chrono>
#include <tuple>
#include <utility>
#include <exception>
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/export.h"
#include "libipc/imp/fmt.h"
#include "libipc/imp/generic.h"
namespace ipc {
namespace log {
enum class level : std::int32_t {
trace,
debug,
info,
warning,
error,
failed,
};
/// \struct template <typename... T> struct context
/// \brief Logging context.
/// \tparam ...T - a log records all parameter types passed
template <typename... T>
struct context {
log::level level;
std::chrono::system_clock::time_point tp;
char const *func;
std::tuple<T...> params;
};
/// \brief Custom defined fmt_to method for imp::fmt
namespace detail_log {
template <typename Tp, std::size_t... I, typename... A>
bool unfold_tuple_fmt_to(fmt_context &ctx, Tp const &tp, std::index_sequence<I...>, A &&...args) {
return fmt_to(ctx, std::forward<A>(args)..., std::get<I>(tp)...);
}
} // namespace detail_log
template <typename... T>
bool context_to_string(fmt_context &f_ctx, context<T...> const &l_ctx) {
static constexpr char types[] = {
'T', 'D', 'I', 'W', 'E', 'F',
};
auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(l_ctx.tp).time_since_epoch().count() % 1000;
return detail_log::unfold_tuple_fmt_to(f_ctx, l_ctx.params, std::index_sequence_for<T...>{},
"[", types[underlyof(l_ctx.level)], "]"
"[", l_ctx.tp, ".", spec("03")(ms), "]"
"[", l_ctx.func, "] ");
}
template <typename... T>
std::string context_to_string(context<T...> const &l_ctx) {
std::string log_txt;
fmt_context f_ctx(log_txt);
LIBIPC_TRY {
if (!context_to_string(f_ctx, l_ctx)) {
return {};
}
f_ctx.finish();
return log_txt;
} LIBIPC_CATCH(...) {
f_ctx.finish();
throw;
}
}
/// \brief Standard console output object.
inline auto &make_std_out() noexcept {
static auto std_out = [](auto const &ctx) {
auto s = context_to_string(ctx);
switch (ctx.level) {
case level::trace:
case level::debug:
case level::info:
std::fprintf(stdout, "%s\n", s.c_str());
break;
case level::warning:
case level::error:
case level::failed:
std::fprintf(stderr, "%s\n", s.c_str());
break;
default:
break;
}
};
return std_out;
}
/// \brief Get the exception information.
inline char const *exception_string(std::exception_ptr eptr) noexcept {
LIBIPC_TRY {
if (eptr) {
std::rethrow_exception(eptr);
}
} LIBIPC_CATCH(std::exception const &e) {
return e.what();
} LIBIPC_CATCH(...) {
return "unknown";
}
return "none";
}
/// \brief Record the last information when an exception occurs.
inline void exception_print(char const *func, std::exception_ptr eptr) noexcept {
if (func == nullptr) {
func = "-";
}
std::fprintf(stderr, "[F][%s] exception: %s\n", func, exception_string(eptr));
}
/**
* \brief Log information base class.
*/
class logger_base {
protected:
char const *func_;
level level_limit_;
logger_base(char const *func, level level_limit) noexcept
: func_ (func)
, level_limit_(level_limit) {}
};
/**
* \brief Log information grips.
*/
template <typename Outputer>
class logger : public logger_base {
Outputer out_;
public:
template <typename O>
logger(char const *func, O &&out, level level_limit) noexcept
: logger_base(func, level_limit)
, out_ (std::forward<O>(out)) {}
template <typename... A>
logger const &operator()(log::level l, A &&...args) const noexcept {
if (underlyof(l) < underlyof(level_limit_)) {
return *this;
}
LIBIPC_TRY {
out_(context<A &&...> {
l, std::chrono::system_clock::now(), func_,
std::forward_as_tuple(std::forward<A>(args)...),
});
} LIBIPC_CATCH(...) {
exception_print(func_, std::current_exception());
}
return *this;
}
template <typename... A> logger const &trace (A &&...args) const noexcept { return (*this)(log::level::trace , std::forward<A>(args)...); }
template <typename... A> logger const &debug (A &&...args) const noexcept { return (*this)(log::level::debug , std::forward<A>(args)...); }
template <typename... A> logger const &info (A &&...args) const noexcept { return (*this)(log::level::info , std::forward<A>(args)...); }
template <typename... A> logger const &warning(A &&...args) const noexcept { return (*this)(log::level::warning, std::forward<A>(args)...); }
template <typename... A> logger const &error (A &&...args) const noexcept { return (*this)(log::level::error , std::forward<A>(args)...); }
template <typename... A> logger const &failed (A &&...args) const noexcept { return (*this)(log::level::failed , std::forward<A>(args)...); }
};
template <typename O>
inline auto make_logger(char const *func, O &&out, level level_limit = level::info) noexcept {
return logger<std::decay_t<O>>(func, std::forward<O>(out), level_limit);
}
inline auto make_logger(char const *func, level level_limit = level::info) noexcept {
return make_logger(func, make_std_out(), level_limit);
}
inline auto make_logger(char const * /*ignore*/, char const *name, level level_limit = level::info) noexcept {
return make_logger(name, make_std_out(), level_limit);
}
#define LIBIPC_LOG(...) \
auto log \
= [](auto &&...args) noexcept { \
return ::ipc::log::make_logger(__func__, std::forward<decltype(args)>(args)...); \
}(__VA_ARGS__)
} // namespace log
} // namespace ipc

View File

@ -0,0 +1,43 @@
/**
* \file libipc/nameof.h
* \author mutouyun (orz@orzz.org)
* \brief Gets the name string of a type.
*/
#pragma once
#include <typeinfo>
#include <string>
#include <cstring>
#include "libipc/imp/export.h"
#include "libipc/imp/span.h"
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \brief The conventional way to obtain demangled symbol name.
* \see https://www.boost.org/doc/libs/1_80_0/libs/core/doc/html/core/demangle.html
*
* \param name the mangled name
* \return std::string a human-readable demangled type name
*/
LIBIPC_EXPORT std::string demangle(std::string name) noexcept;
/**
* \brief Returns an implementation defined string containing the name of the type.
* \see https://en.cppreference.com/w/cpp/types/type_info/name
*
* \tparam T a type
* \return std::string a human-readable demangled type name
*/
template <typename T>
std::string nameof() noexcept {
LIBIPC_TRY {
return demangle(typeid(T).name());
} LIBIPC_CATCH(...) {
return {};
}
}
} // namespace ipc

138
include/libipc/imp/result.h Normal file
View File

@ -0,0 +1,138 @@
/**
* \file libipc/result.h
* \author mutouyun (orz@orzz.org)
* \brief Define the return value type with an error status code.
*/
#pragma once
#include <type_traits>
#include <utility>
#include <cstdint>
#include "libipc/imp/expected.h"
#include "libipc/imp/error.h"
#include "libipc/imp/generic.h"
#include "libipc/imp/fmt.h"
namespace ipc {
namespace detail_result {
template <typename T>
struct generic_initializer {
using storage_t = expected<T, std::error_code>;
/// \brief Custom initialization.
static constexpr storage_t init_code() noexcept {
return {unexpected, std::error_code(-1, std::generic_category())};
}
static constexpr storage_t init_code(T value) noexcept {
return {in_place, value};
}
static constexpr storage_t init_code(std::error_code const &ec) noexcept {
return {unexpected, ec};
}
};
template <typename T, typename = void>
struct result_base;
template <typename T>
struct result_base<T, std::enable_if_t<std::is_pointer<T>::value>>
: generic_initializer<T> {
using storage_t = typename generic_initializer<T>::storage_t;
using generic_initializer<T>::init_code;
static constexpr storage_t init_code(std::nullptr_t, std::error_code const &ec) noexcept {
return {unexpected, ec};
}
static constexpr storage_t init_code(std::nullptr_t) noexcept {
return {unexpected, std::error_code(-1, std::generic_category())};
}
};
template <typename T>
struct result_base<T, std::enable_if_t<std::is_integral<T>::value || std::is_enum<T>::value>>
: generic_initializer<T> {
using storage_t = typename generic_initializer<T>::storage_t;
using generic_initializer<T>::init_code;
};
} // namespace detail_result
/**
* \class class result
* \brief The generic wrapper for the result type.
*/
template <typename T>
class result : public detail_result::result_base<T> {
private:
using base_t = detail_result::result_base<T>;
using storage_t = typename base_t::storage_t;
storage_t ret_; ///< internal data
public:
template <typename... A,
typename = not_match<result, A...>,
typename = decltype(base_t::init_code(std::declval<A>()...))>
result(A &&...args) noexcept
: ret_(base_t::init_code(std::forward<A>(args)...)) {}
std::string format_string() const {
if LIBIPC_LIKELY(ret_) {
return fmt("value = ", ret_.value());
} else {
return fmt("error = ", ret_.error());
}
}
T value() const noexcept { return ret_ ? ret_.value() : T{}; }
bool ok () const noexcept { return ret_.has_value(); }
std::error_code error() const noexcept { return ret_.error(); }
T operator * () const noexcept { return value(); }
explicit operator bool() const noexcept { return ok (); }
friend bool operator==(result const &lhs, result const &rhs) noexcept { return lhs.ret_ == rhs.ret_; }
friend bool operator!=(result const &lhs, result const &rhs) noexcept { return !(lhs == rhs); }
};
template <>
class result<void> {
private:
std::error_code ret_; ///< internal data
public:
result() noexcept
: ret_(-1, std::generic_category()) {}
result(std::error_code const &ec) noexcept
: ret_(ec) {}
std::string format_string() const {
return fmt("error = ", error());
}
bool ok () const noexcept { return !ret_; }
std::error_code error() const noexcept { return ret_; }
explicit operator bool () const noexcept { return ok(); }
friend bool operator==(result const &lhs, result const &rhs) noexcept { return lhs.ret_ == rhs.ret_; }
friend bool operator!=(result const &lhs, result const &rhs) noexcept { return !(lhs == rhs); }
};
/// \brief Custom defined fmt_to method for imp::fmt
namespace detail_tag_invoke {
template <typename T>
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, result<T> const &r) {
return fmt_to(ctx, (r ? "succ" : "fail"), ", ", r.format_string());
}
} // namespace detail_tag_invoke
} // namespace ipc

View File

@ -0,0 +1,77 @@
/**
* \file libipc/scope_exit.h
* \author mutouyun (orz@orzz.org)
* \brief Execute guard function when the enclosing scope exits.
*/
#pragma once
#include <utility> // std::forward, std::move
#include <functional> // std::function
#include <type_traits>
#include "libipc/imp/detect_plat.h"
namespace ipc {
template <typename F = std::function<void()>>
class scope_exit {
F destructor_;
mutable bool released_;
public:
template <typename G>
explicit scope_exit(G &&destructor) noexcept
: destructor_(std::forward<G>(destructor))
, released_ (false) {}
scope_exit(scope_exit &&other) noexcept
: destructor_(std::move(other.destructor_))
, released_ (std::exchange(other.released_, true)) /*release rhs*/ {}
scope_exit &operator=(scope_exit &&other) noexcept {
destructor_ = std::move(other.destructor_);
released_ = std::exchange(other.released_, true);
return *this;
}
~scope_exit() noexcept {
if (!released_) destructor_();
}
void release() const noexcept {
released_ = true;
}
void do_exit() noexcept {
if (released_) return;
destructor_();
released_ = true;
}
void swap(scope_exit &other) noexcept {
std::swap(destructor_, other.destructor_);
std::swap(released_ , other.released_);
}
};
/// \brief Creates a scope_exit object.
template <typename F>
auto make_scope_exit(F &&destructor) noexcept {
return scope_exit<std::decay_t<F>>(std::forward<F>(destructor));
}
namespace detail_scope_exit {
struct scope_exit_helper {
template <typename F>
auto operator=(F &&f) const noexcept {
return make_scope_exit(std::forward<F>(f));
}
};
} // namespace detail_scope_exit
#define LIBIPC_SCOPE_EXIT($VAL) \
LIBIPC_UNUSED auto $VAL = ::ipc::detail_scope_exit::scope_exit_helper{}
} // namespace ipc

281
include/libipc/imp/span.h Normal file
View File

@ -0,0 +1,281 @@
/**
* \file libipc/span.h
* \author mutouyun (orz@orzz.org)
* \brief Describes an object that can refer to a contiguous sequence of objects.
*/
#pragma once
#include <type_traits>
#include <cstddef>
#include <iterator>
#include <array>
#include <vector>
#include <string>
#include <limits>
#include <algorithm>
#include <cstdint>
#include <initializer_list>
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/generic.h"
#if defined(LIBIPC_CPP_20) && defined(__cpp_lib_span)
#include <span>
#define LIBIPC_CPP_LIB_SPAN_
#endif // __cpp_lib_span
namespace ipc {
namespace detail_span {
/// \brief Helper trait for span.
template <typename From, typename To>
using array_convertible = std::is_convertible<From(*)[], To(*)[]>;
template <typename From, typename To>
using is_array_convertible =
typename std::enable_if<array_convertible<From, To>::value>::type;
template <typename T, typename Ref>
using compatible_ref = array_convertible<typename std::remove_reference<Ref>::type, T>;
template <typename It>
using iter_reference_t = decltype(*std::declval<It&>());
template <typename T, typename It>
using is_compatible_iter =
typename std::enable_if<compatible_ref<T, iter_reference_t<It>>::value>::type;
template <typename From, typename To>
using is_inconvertible =
typename std::enable_if<!std::is_convertible<From, To>::value>::type;
template <typename S, typename I>
using is_sized_sentinel_for =
typename std::enable_if<std::is_convertible<decltype(std::declval<S>() - std::declval<I>()),
std::ptrdiff_t>::value>::type;
template <typename T>
using is_continuous_container =
decltype(dataof(std::declval<T>()), countof(std::declval<T>()));
/// \brief Obtain the address represented by p
/// without forming a reference to the object pointed to by p.
/// \see https://en.cppreference.com/w/cpp/memory/to_address
template<typename T>
constexpr T *to_address(T *ptr) noexcept {
static_assert(!std::is_function<T>::value, "ptr shouldn't a function pointer");
return ptr;
}
template<typename T>
constexpr auto to_address(T const &ptr)
noexcept(noexcept(ptr.operator->()))
-> decltype(ptr.operator->()) {
return to_address(ptr.operator->());
}
} // namespace detail_span
/**
* \brief A simple implementation of span.
* \see https://en.cppreference.com/w/cpp/container/span
*/
template <typename T>
class span {
public:
using element_type = T;
using value_type = typename std::remove_cv<element_type>::type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = element_type *;
using const_pointer = typename std::remove_const<element_type>::type const *;
using reference = element_type &;
using const_reference = typename std::remove_const<element_type>::type const &;
using iterator = pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
private:
pointer ptr_ {nullptr};
size_type extent_ {0};
public:
constexpr span() noexcept = default;
constexpr span(span const &) noexcept = default;
#if (LIBIPC_CC_MSVC > LIBIPC_CC_MSVC_2015)
constexpr
#endif
span & operator=(span const &) noexcept = default;
template <typename It,
typename = detail_span::is_compatible_iter<T, It>>
constexpr span(It first, size_type count) noexcept
: ptr_ (detail_span::to_address(first))
, extent_(count) {}
template <typename It, typename End,
typename = detail_span::is_compatible_iter<T, It>,
typename = detail_span::is_sized_sentinel_for<End, It>,
typename = detail_span::is_inconvertible<End, size_type>>
constexpr span(It first, End last) noexcept(noexcept(last - first))
: ptr_ (detail_span::to_address(first))
, extent_(static_cast<size_type>(last - first)) {}
template <typename U,
typename = detail_span::is_continuous_container<U>,
typename = detail_span::is_array_convertible<std::remove_pointer_t<
decltype(dataof(std::declval<U>()))>, element_type>>
constexpr span(U &&u) noexcept(noexcept(dataof (std::forward<U>(u)),
countof(std::forward<U>(u))))
: ptr_ (dataof (std::forward<U>(u)))
, extent_(countof(std::forward<U>(u))) {}
template <typename U, std::size_t E,
typename = detail_span::is_array_convertible<U, element_type>>
constexpr span(U (&arr)[E]) noexcept
: span(static_cast<pointer>(arr), E) {}
template <typename U, std::size_t E,
typename = detail_span::is_array_convertible<U, element_type>>
constexpr span(std::array<U, E> &arr) noexcept
: span(static_cast<pointer>(arr.data()), E) {}
template <typename U, std::size_t E,
typename = detail_span::is_array_convertible<typename std::add_const<U>::type, element_type>>
constexpr span(std::array<U, E> const &arr) noexcept
: span(static_cast<pointer>(arr.data()), E) {}
template <typename U,
typename = detail_span::is_array_convertible<U, T>>
constexpr span(span<U> const &s) noexcept
: ptr_ (s.data())
, extent_(s.size()) {}
#ifdef LIBIPC_CPP_LIB_SPAN_
template <typename U, std::size_t E,
typename = detail_span::is_array_convertible<U, T>>
constexpr span(std::span<U, E> const &s) noexcept
: ptr_ (s.data())
, extent_(s.size()) {}
#endif // LIBIPC_CPP_LIB_SPAN_
constexpr size_type size() const noexcept {
return extent_;
}
constexpr size_type size_bytes() const noexcept {
return size() * sizeof(element_type);
}
constexpr bool empty() const noexcept {
return size() == 0;
}
constexpr pointer data() const noexcept {
return this->ptr_;
}
constexpr reference front() const noexcept {
return *data();
}
constexpr reference back() const noexcept {
return *(data() + (size() - 1));
}
constexpr reference operator[](size_type idx) const noexcept {
return *(data() + idx);
}
constexpr iterator begin() const noexcept {
return iterator(data());
}
constexpr iterator end() const noexcept {
return iterator(data() + this->size());
}
constexpr reverse_iterator rbegin() const noexcept {
return reverse_iterator(this->end());
}
constexpr reverse_iterator rend() const noexcept {
return reverse_iterator(this->begin());
}
constexpr span first(size_type count) const noexcept {
return span(begin(), count);
}
constexpr span last(size_type count) const noexcept {
return span(end() - count, count);
}
constexpr span subspan(size_type offset, size_type count = (std::numeric_limits<size_type>::max)()) const noexcept {
return (offset >= size()) ? span() : span(begin() + offset, (std::min)(size() - offset, count));
}
};
/// \brief Support for span equals comparison.
template <typename T, typename U,
typename = decltype(std::declval<T>() == std::declval<U>())>
bool operator==(span<T> a, span<U> b) noexcept {
if (a.size() != b.size()) {
return false;
}
for (std::size_t i = 0; i < a.size(); ++i) {
if (a[i] != b[i]) return false;
}
return true;
}
/// \brief Constructs an object of type T and wraps it in a span.
/// Before C++17, template argument deduction for class templates was not supported.
/// \see https://en.cppreference.com/w/cpp/language/template_argument_deduction
template <typename T>
auto make_span(T *arr, std::size_t count) noexcept -> span<T> {
return {arr, count};
}
template <typename T, std::size_t E>
auto make_span(T (&arr)[E]) noexcept -> span<T> {
return {arr};
}
template <typename T, std::size_t E>
auto make_span(std::array<T, E> &arr) noexcept -> span<T> {
return {arr};
}
template <typename T, std::size_t E>
auto make_span(std::array<T, E> const &arr) noexcept -> span<typename std::add_const<T>::type> {
return {arr};
}
template <typename T>
auto make_span(std::vector<T> &arr) noexcept -> span<T> {
return {arr.data(), arr.size()};
}
template <typename T>
auto make_span(std::vector<T> const &arr) noexcept -> span<typename std::add_const<T>::type> {
return {arr.data(), arr.size()};
}
template <typename T>
auto make_span(std::initializer_list<T> list) noexcept -> span<typename std::add_const<T>::type> {
return {list.begin(), list.end()};
}
inline auto make_span(std::string &str) noexcept -> span<char> {
return {const_cast<char *>(str.data()), str.size()};
}
inline auto make_span(std::string const &str) noexcept -> span<char const> {
return {str.data(), str.size()};
}
} // namespace ipc

View File

@ -0,0 +1,33 @@
/**
* \file libipc/system.h
* \author mutouyun (orz@orzz.org)
* \brief Isolation and encapsulation of system APIs.
*/
#pragma once
#include <string>
#include <ostream> // std::ostream
#include <cstdint>
#include "libipc/imp/export.h"
#include "libipc/imp/error.h"
#include "libipc/imp/result.h"
namespace ipc {
namespace sys {
/// \brief A platform-dependent error code.
LIBIPC_EXPORT std::error_code error() noexcept;
/// \enum The name of the `conf()` argument used to inquire about its value.
/// \brief Certain options are supported,
/// or what the value is of certain configurable constants or limits.
enum class info : std::int32_t {
page_size,
};
/// \brief Get system configuration information at run time.
LIBIPC_EXPORT result<std::int64_t> conf(info) noexcept;
} // namespace sys
} // namespace ipc

View File

@ -0,0 +1,163 @@
/**
* \file libipc/uninitialized.h
* \author mutouyun (orz@orzz.org)
* \brief Uninitialized memory algorithms.
*/
#pragma once
#include <new> // placement-new
#include <type_traits> // std::enable_if_t
#include <utility> // std::forward
#include <memory> // std::construct_at, std::destroy_at, std::addressof
#include <cstddef>
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/generic.h"
namespace ipc {
/**
* \brief Creates an object at a given address, like 'construct_at' in c++20
* \see https://en.cppreference.com/w/cpp/memory/construct_at
*/
// Overload for zero arguments - use value initialization
template <typename T>
T* construct(void *p) {
#if defined(LIBIPC_CPP_20)
return std::construct_at(static_cast<T *>(p));
#else
return ::new (p) T();
#endif
}
// Overload for one or more arguments - prefer direct initialization
template <typename T, typename A1, typename... A>
auto construct(void *p, A1 &&arg1, A &&...args)
-> std::enable_if_t<::std::is_constructible<T, A1, A...>::value, T *> {
#if defined(LIBIPC_CPP_20)
return std::construct_at(static_cast<T *>(p), std::forward<A1>(arg1), std::forward<A>(args)...);
#else
return ::new (p) T(std::forward<A1>(arg1), std::forward<A>(args)...);
#endif
}
// Overload for non-constructible types - use aggregate initialization
template <typename T, typename A1, typename... A>
auto construct(void *p, A1 &&arg1, A &&...args)
-> std::enable_if_t<!::std::is_constructible<T, A1, A...>::value, T *> {
return ::new (p) T{std::forward<A1>(arg1), std::forward<A>(args)...};
}
/**
* \brief Destroys an object at a given address, like 'destroy_at' in c++17
* \see https://en.cppreference.com/w/cpp/memory/destroy_at
*/
template <typename T>
void *destroy(T *p) noexcept {
if (p == nullptr) return nullptr;
#if defined(LIBIPC_CPP_17)
std::destroy_at(p);
#else
p->~T();
#endif
return p;
}
template <>
inline void *destroy<void>(void *p) noexcept {
return p;
}
template <typename T, std::size_t N>
void *destroy(T (*p)[N]) noexcept {
if (p == nullptr) return nullptr;
#if defined(LIBIPC_CPP_20)
std::destroy_at(p);
#elif defined(LIBIPC_CPP_17)
std::destroy(std::begin(*p), std::end(*p));
#else
for (auto &elem : *p) destroy(std::addressof(elem));
#endif
return p;
}
/**
* \brief Destroys a range of objects.
* \see https://en.cppreference.com/w/cpp/memory/destroy
*/
template <typename ForwardIt>
void destroy(ForwardIt first, ForwardIt last) noexcept {
#if defined(LIBIPC_CPP_17)
std::destroy(first, last);
#else
for (; first != last; ++first) {
destroy(std::addressof(*first));
}
#endif
}
/**
* \brief Destroys a number of objects in a range.
* \see https://en.cppreference.com/w/cpp/memory/destroy_n
*/
template <typename ForwardIt, typename Size>
ForwardIt destroy_n(ForwardIt first, Size n) noexcept {
#if defined(LIBIPC_CPP_17)
return std::destroy_n(first, n);
#else
for (; n > 0; (void) ++first, --n)
destroy(std::addressof(*first));
return first;
#endif
}
/**
* \brief Constructs objects by default-initialization
* in an uninitialized area of memory, defined by a start and a count.
* \see https://en.cppreference.com/w/cpp/memory/uninitialized_default_construct_n
*/
template <typename ForwardIt, typename Size>
ForwardIt uninitialized_default_construct_n(ForwardIt first, Size n) {
#if defined(LIBIPC_CPP_17)
return std::uninitialized_default_construct_n(first, n);
#else
using T = typename std::iterator_traits<ForwardIt>::value_type;
ForwardIt current = first;
LIBIPC_TRY {
for (; n > 0; (void) ++current, --n)
::new (horrible_cast<void *>(std::addressof(*current))) T;
return current;
} LIBIPC_CATCH(...) {
destroy(first, current);
LIBIPC_THROW(, first);
}
#endif
}
/**
* \brief Moves a number of objects to an uninitialized area of memory.
* \see https://en.cppreference.com/w/cpp/memory/uninitialized_move_n
*/
template <typename InputIt, typename Size, typename NoThrowForwardIt>
auto uninitialized_move_n(InputIt first, Size count, NoThrowForwardIt d_first)
-> std::pair<InputIt, NoThrowForwardIt> {
#if defined(LIBIPC_CPP_17)
return std::uninitialized_move_n(first, count, d_first);
#else
using Value = typename std::iterator_traits<NoThrowForwardIt>::value_type;
NoThrowForwardIt current = d_first;
LIBIPC_TRY {
for (; count > 0; ++first, (void) ++current, --count) {
::new (static_cast<void *>(std::addressof(*current))) Value(std::move(*first));
}
} LIBIPC_CATCH(...) {
destroy(d_first, current);
LIBIPC_THROW(, {first, d_first});
}
return {first, current};
#endif
}
} // namespace ipc

View File

@ -2,7 +2,7 @@
#include <string>
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
#include "libipc/buffer.h"
#include "libipc/shm.h"
@ -18,7 +18,7 @@ enum : unsigned {
};
template <typename Flag>
struct IPC_EXPORT chan_impl {
struct LIBIPC_EXPORT chan_impl {
static ipc::handle_t init_first();
static bool connect (ipc::handle_t * ph, char const * name, unsigned mode);

View File

@ -0,0 +1,121 @@
/**
* \file libipc/block_pool.h
* \author mutouyun (orz@orzz.org)
* \brief The fixed-length memory block pool.
*/
#pragma once
#include <cstddef>
#include "libipc/mem/central_cache_pool.h"
namespace ipc {
namespace mem {
/**
* \brief Fixed-length memory block pool.
* \tparam BlockSize specifies the memory block size
* \tparam BlockPoolExpansion specifies the default number of blocks to expand when the block pool is exhausted
*/
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_pool;
/// \brief General-purpose block pool for any size of memory block.
/// \note This block pool can only be used to deallocate a group of memory blocks of unknown but consistent size,
/// and cannot be used for memory block allocation.
template <>
class block_pool<0, 0> {
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
friend class block_pool;
/// \brief The block type.
struct block_t {
block_t *next;
};
/// \brief The central cache pool type.
using central_cache_pool_t = central_cache_pool<block_t, 0>;
public:
static constexpr std::size_t block_size = 0;
block_pool() noexcept : cursor_(central_cache_pool_t::instance().aqueire()) {}
~block_pool() noexcept {
central_cache_pool_t::instance().release(cursor_);
}
block_pool(block_pool const &) = delete;
block_pool& operator=(block_pool const &) = delete;
block_pool(block_pool &&rhs) noexcept : cursor_(std::exchange(rhs.cursor_, nullptr)) {}
block_pool &operator=(block_pool &&) noexcept = delete;
void deallocate(void *p) noexcept {
if (p == nullptr) return;
block_t *b = static_cast<block_t *>(p);
b->next = cursor_;
cursor_ = b;
}
private:
block_t *cursor_;
};
/// \brief A block pool for a block of memory of a specific size.
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_pool {
/// \brief The block type.
using block_t = block<BlockSize>;
/// \brief The central cache pool type.
using central_cache_pool_t = central_cache_pool<block_t, BlockPoolExpansion>;
/// \brief Expand the block pool when it is exhausted.
block_t *expand() noexcept {
return central_cache_pool_t::instance().aqueire();
}
public:
static constexpr std::size_t block_size = BlockSize;
block_pool() noexcept : cursor_(expand()) {}
~block_pool() noexcept {
central_cache_pool_t::instance().release(cursor_);
}
block_pool(block_pool const &) = delete;
block_pool& operator=(block_pool const &) = delete;
block_pool(block_pool &&rhs) noexcept
: cursor_(std::exchange(rhs.cursor_, nullptr)) {}
block_pool &operator=(block_pool &&) noexcept = delete;
/// \brief Used to take all memory blocks from within a general-purpose block pool.
/// \note Of course, the actual memory blocks they manage must be the same size.
block_pool(block_pool<0, 0> &&rhs) noexcept
: cursor_(reinterpret_cast<block_t *>(std::exchange(rhs.cursor_, nullptr))) {}
void *allocate() noexcept {
if (cursor_ == nullptr) {
cursor_ = expand();
if (cursor_ == nullptr) return nullptr;
}
block_t *p = cursor_;
cursor_ = cursor_->next;
return p->storage.data();
}
void deallocate(void *p) noexcept {
if (p == nullptr) return;
block_t *b = static_cast<block_t *>(p);
b->next = cursor_;
cursor_ = b;
}
private:
block_t *cursor_;
};
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,163 @@
/**
* \file libipc/bytes_allocator.h
* \author mutouyun (orz@orzz.org)
* \brief A generic polymorphic memory allocator.
*/
#pragma once
#include <type_traits>
#include <array>
#include <limits> // std::numeric_limits
#include <utility> // std::forward
#include <tuple> // std::ignore
#include <cstddef>
#include "libipc/imp/export.h"
#include "libipc/imp/uninitialized.h"
#include "libipc/imp/byte.h"
namespace ipc {
namespace mem {
/// \brief Helper trait for memory resource.
template <typename T, typename = void>
struct has_allocate : std::false_type {};
template <typename T>
struct has_allocate<T,
typename std::enable_if<std::is_convertible<
decltype(std::declval<T &>().allocate(std::declval<std::size_t>(),
std::declval<std::size_t>())), void *
>::value>::type> : std::true_type {};
template <typename T, typename = void>
struct has_deallocate : std::false_type {};
template <typename T>
struct has_deallocate<T,
decltype(std::declval<T &>().deallocate(std::declval<void *>(),
std::declval<std::size_t>(),
std::declval<std::size_t>()))
> : std::true_type {};
template <typename T>
using is_memory_resource =
std::enable_if_t<has_allocate <T>::value &&
has_deallocate<T>::value, bool>;
/**
* \brief An allocator which exhibits different allocation behavior
* depending upon the memory resource from which it is constructed.
*
* \note Unlike `std::pmr::container_allocator`, it does not
* rely on a specific inheritance relationship and only restricts
* the interface behavior of the incoming memory resource object to
* conform to `std::pmr::memory_resource`.
*
* \see https://en.cppreference.com/w/cpp/memory/memory_resource
* https://en.cppreference.com/w/cpp/memory/container_allocator
*/
class LIBIPC_EXPORT bytes_allocator {
class holder_mr_base {
public:
virtual ~holder_mr_base() noexcept = default;
virtual void *alloc(std::size_t, std::size_t) const = 0;
virtual void dealloc(void *, std::size_t, std::size_t) const = 0;
};
template <typename MR, typename = bool>
class holder_mr;
/**
* \brief An empty holding class used to calculate a reasonable memory size for the holder.
* \tparam MR cannot be converted to the type of memory resource
*/
template <typename MR, typename U>
class holder_mr : public holder_mr_base {
protected:
MR *res_;
public:
holder_mr(MR *p_mr) noexcept
: res_(p_mr) {}
// [MSVC] error C2259: 'bytes_allocator::holder_mr<void *,bool>': cannot instantiate abstract class.
void *alloc(std::size_t s, std::size_t a) const override { return nullptr; }
void dealloc(void *p, std::size_t s, std::size_t a) const override {}
};
/**
* \brief A memory resource pointer holder class for type erasure.
* \tparam MR memory resource type
*/
template <typename MR>
class holder_mr<MR, is_memory_resource<MR>> : public holder_mr<MR, void> {
using base_t = holder_mr<MR, void>;
public:
holder_mr(MR *p_mr) noexcept
: base_t{p_mr} {}
void *alloc(std::size_t s, std::size_t a) const override {
return base_t::res_->allocate(s, a);
}
void dealloc(void *p, std::size_t s, std::size_t a) const override {
base_t::res_->deallocate(p, s, a);
}
};
using void_holder_t = holder_mr<void *>;
alignas(void_holder_t) std::array<ipc::byte, sizeof(void_holder_t)> holder_;
holder_mr_base & get_holder() noexcept;
holder_mr_base const &get_holder() const noexcept;
void init_default_resource() noexcept;
public:
/// \brief Constructs an `bytes_allocator` using the return value of
/// `new_delete_resource::get()` as the underlying memory resource.
bytes_allocator() noexcept;
~bytes_allocator() noexcept;
bytes_allocator(bytes_allocator const &other) noexcept = default;
bytes_allocator &operator=(bytes_allocator const &other) & noexcept = default;
bytes_allocator(bytes_allocator &&other) noexcept = default;
bytes_allocator &operator=(bytes_allocator &&other) & noexcept = default;
/// \brief Constructs a `bytes_allocator` from a memory resource pointer.
/// \note The lifetime of the pointer must be longer than that of bytes_allocator.
template <typename T, is_memory_resource<T> = true>
bytes_allocator(T *p_mr) noexcept {
if (p_mr == nullptr) {
init_default_resource();
return;
}
std::ignore = ipc::construct<holder_mr<T>>(holder_.data(), p_mr);
}
void swap(bytes_allocator &other) noexcept;
/// \brief Allocate/deallocate memory.
void *allocate(std::size_t s, std::size_t = alignof(std::max_align_t)) const;
void deallocate(void *p, std::size_t s, std::size_t = alignof(std::max_align_t)) const;
/// \brief Allocates uninitialized memory and constructs an object of type T in the memory.
template <typename T, typename... A>
T *construct(A &&...args) const {
return ipc::construct<T>(allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
}
/// \brief Calls the destructor of the object pointed to by p and deallocates the memory.
template <typename T>
void destroy(T *p) const noexcept {
deallocate(ipc::destroy(p), sizeof(T), alignof(T));
}
};
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,20 @@
/**
* \file libipc/central_cache_allocator.h
* \author mutouyun (orz@orzz.org)
* \brief The central cache allocator getter.
*/
#pragma once
#include "libipc/imp/export.h"
#include "libipc/mem/bytes_allocator.h"
namespace ipc {
namespace mem {
/// \brief Get the central cache allocator.
/// \note The central cache allocator is used to allocate memory for the central cache pool.
/// The underlying memory resource is a `monotonic_buffer_resource` with a fixed-size buffer.
LIBIPC_EXPORT bytes_allocator &central_cache_allocator() noexcept;
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,132 @@
/**
* \file libipc/central_cache_pool.h
* \author mutouyun (orz@orzz.org)
* \brief The fixed-length memory block central cache pool.
*/
#pragma once
#include <cstddef>
#include <deque>
#include <utility>
#include <array>
#include "libipc/imp/byte.h"
#include "libipc/concur/intrusive_stack.h"
#include "libipc/mem/central_cache_allocator.h"
namespace ipc {
namespace mem {
/**
* \brief The block type.
* \tparam BlockSize specifies the memory block size
*/
template <std::size_t BlockSize>
union block {
block *next;
alignas(std::max_align_t) std::array<byte, BlockSize> storage;
};
/**
* \brief A fixed-length memory block central cache pool.
* \tparam BlockT specifies the memory block type
*/
template <typename BlockT, std::size_t BlockPoolExpansion>
class central_cache_pool {
/// \brief The block type, which should be a union of a pointer and a storage.
using block_t = BlockT;
/// \brief The chunk type, which is an array of blocks.
using chunk_t = std::array<block_t, BlockPoolExpansion>;
/// \brief The node type, which is used to store the block pointer.
using node_t = typename concur::intrusive_stack<block_t *>::node;
/// \brief The central cache stack.
concur::intrusive_stack<block_t *> cached_;
concur::intrusive_stack<block_t *> aqueired_;
central_cache_pool() noexcept = default;
public:
block_t *aqueire() noexcept {
auto *n = cached_.pop();
if (n != nullptr) {
aqueired_.push(n);
return n->value;
}
auto *chunk = central_cache_allocator().construct<chunk_t>();
if (chunk == nullptr) {
return nullptr;
}
for (std::size_t i = 0; i < BlockPoolExpansion - 1; ++i) {
(*chunk)[i].next = &(*chunk)[i + 1];
}
chunk->back().next = nullptr;
return chunk->data();
}
void release(block_t *p) noexcept {
if (p == nullptr) return;
auto *a = aqueired_.pop();
if (a == nullptr) {
a = central_cache_allocator().construct<node_t>();
if (a == nullptr) return;
}
a->value = p;
cached_.push(a);
}
/// \brief Get the singleton instance.
static central_cache_pool &instance() noexcept {
static central_cache_pool pool;
return pool;
}
};
/// \brief A fixed-length memory block central cache pool with no default expansion size.
template <typename BlockT>
class central_cache_pool<BlockT, 0> {
/// \brief The block type, which should be a union of a pointer and a storage.
using block_t = BlockT;
/// \brief The node type, which is used to store the block pointer.
using node_t = typename concur::intrusive_stack<block_t *>::node;
/// \brief The central cache stack.
concur::intrusive_stack<block_t *> cached_;
concur::intrusive_stack<block_t *> aqueired_;
central_cache_pool() noexcept = default;
public:
block_t *aqueire() noexcept {
auto *n = cached_.pop();
if (n != nullptr) {
aqueired_.push(n);
return n->value;
}
// For pools with no default expansion size,
// the central cache pool is only buffered, not allocated.
return nullptr;
}
void release(block_t *p) noexcept {
if (p == nullptr) return;
auto *a = aqueired_.pop();
if (a == nullptr) {
a = central_cache_allocator().construct<node_t>();
if (a == nullptr) return;
}
a->value = p;
cached_.push(a);
}
/// \brief Get the singleton instance.
static central_cache_pool &instance() noexcept {
static central_cache_pool pool;
return pool;
}
};
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,101 @@
/**
* \file libipc/container_allocator.h
* \author mutouyun (orz@orzz.org)
* \brief An allocator that can be used by all standard library containers.
*/
#pragma once
#include <cstddef>
#include <utility>
#include <limits>
#include "libipc/imp/uninitialized.h"
#include "libipc/mem/new.h"
namespace ipc {
namespace mem {
/**
* \brief An allocator that can be used by all standard library containers.
*
* \see https://en.cppreference.com/w/cpp/memory/allocator
* https://en.cppreference.com/w/cpp/memory/polymorphic_allocator
*/
template <typename T>
class container_allocator {
template <typename U>
friend class container_allocator;
public:
// type definitions
typedef T value_type;
typedef value_type * pointer;
typedef const value_type *const_pointer;
typedef value_type & reference;
typedef const value_type &const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// the other type of std_allocator
template <typename U>
struct rebind {
using other = container_allocator<U>;
};
container_allocator() noexcept {}
// construct by copying (do nothing)
container_allocator (container_allocator<T> const &) noexcept {}
container_allocator& operator=(container_allocator<T> const &) noexcept { return *this; }
// construct from a related allocator (do nothing)
template <typename U> container_allocator (container_allocator<U> const &) noexcept {}
template <typename U> container_allocator &operator=(container_allocator<U> const &) noexcept { return *this; }
container_allocator (container_allocator &&) noexcept = default;
container_allocator& operator=(container_allocator &&) noexcept = default;
constexpr size_type max_size() const noexcept {
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
}
pointer allocate(size_type count) noexcept {
if (count == 0) return nullptr;
if (count > this->max_size()) return nullptr;
// Allocate raw memory without constructing objects
// Construction should be done by construct() member function
void *p = mem::alloc(sizeof(value_type) * count);
return static_cast<pointer>(p);
}
void deallocate(pointer p, size_type count) noexcept {
if (count == 0) return;
if (count > this->max_size()) return;
// Deallocate raw memory without destroying objects
// Destruction should be done by destroy() member function before deallocate
mem::free(p, sizeof(value_type) * count);
}
template <typename... P>
static void construct(pointer p, P && ... params) {
std::ignore = ipc::construct<T>(p, std::forward<P>(params)...);
}
static void destroy(pointer p) {
std::ignore = ipc::destroy(p);
}
};
template <typename T, typename U>
constexpr bool operator==(container_allocator<T> const &, container_allocator<U> const &) noexcept {
return true;
}
template <typename T, typename U>
constexpr bool operator!=(container_allocator<T> const &, container_allocator<U> const &) noexcept {
return false;
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,83 @@
/**
* \file libipc/memory_resource.h
* \author mutouyun (orz@orzz.org)
* \brief Implement memory allocation strategies that can be used by ipc::mem::bytes_allocator.
*/
#pragma once
#include <type_traits>
#include <cstddef> // std::size_t, std::max_align_t
#include "libipc/imp/export.h"
#include "libipc/imp/span.h"
#include "libipc/imp/byte.h"
#include "libipc/mem/bytes_allocator.h"
namespace ipc {
namespace mem {
/**
* \class LIBIPC_EXPORT new_delete_resource
* \brief A memory resource that uses the
* standard memory allocation and deallocation interface to allocate memory.
* \see https://en.cppreference.com/w/cpp/memory/new_delete_resource
*/
class LIBIPC_EXPORT new_delete_resource {
public:
/// \brief Returns a pointer to a `new_delete_resource`.
static new_delete_resource *get() noexcept;
/// \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.
/// \remark Returns nullptr if storage of the requested size and alignment cannot be obtained.
/// \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
/// \brief Deallocates the storage pointed to by p.
/// \see https://en.cppreference.com/w/cpp/memory/memory_resource/deallocate
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
};
/**
* \class LIBIPC_EXPORT monotonic_buffer_resource
* \brief A special-purpose memory resource class
* that releases the allocated memory only when the resource is destroyed.
* \see https://en.cppreference.com/w/cpp/memory/monotonic_buffer_resource
*/
class LIBIPC_EXPORT monotonic_buffer_resource {
bytes_allocator upstream_;
struct node {
node *next;
std::size_t size;
} *free_list_;
ipc::byte * head_;
ipc::byte * tail_;
std::size_t next_size_;
ipc::byte * const initial_buffer_;
std::size_t const initial_size_;
public:
monotonic_buffer_resource() noexcept;
explicit monotonic_buffer_resource(bytes_allocator upstream) noexcept;
explicit monotonic_buffer_resource(std::size_t initial_size) noexcept;
monotonic_buffer_resource(std::size_t initial_size, bytes_allocator upstream) noexcept;
monotonic_buffer_resource(ipc::span<ipc::byte> buffer) noexcept;
monotonic_buffer_resource(ipc::span<ipc::byte> buffer, bytes_allocator upstream) noexcept;
~monotonic_buffer_resource() noexcept;
monotonic_buffer_resource(monotonic_buffer_resource const &) = delete;
monotonic_buffer_resource &operator=(monotonic_buffer_resource const &) = delete;
bytes_allocator upstream_resource() const noexcept;
void release() noexcept;
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
};
} // namespace mem
} // namespace ipc

117
include/libipc/mem/new.h Normal file
View File

@ -0,0 +1,117 @@
/**
* \file libipc/mem.h
* \author mutouyun (orz@orzz.org)
* \brief Global memory management.
*/
#pragma once
#include <cstddef>
#include <algorithm>
#include <type_traits>
#include <limits>
#include <tuple>
#include "libipc/imp/aligned.h"
#include "libipc/imp/uninitialized.h"
#include "libipc/imp/byte.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/export.h"
#include "libipc/mem/memory_resource.h"
#include "libipc/mem/block_pool.h"
namespace ipc {
namespace mem {
/// \brief Defines the memory block collector interface.
class LIBIPC_EXPORT block_collector {
public:
virtual ~block_collector() noexcept = default;
virtual void *allocate(std::size_t /*bytes*/) noexcept = 0;
virtual void deallocate(void */*p*/, std::size_t /*bytes*/) noexcept = 0;
};
/// \brief Matches the appropriate memory block resource based on a specified size.
LIBIPC_EXPORT block_collector &get_regular_resource(std::size_t s) noexcept;
/// \brief Allocates storage with a size of at least bytes bytes.
LIBIPC_EXPORT void *alloc(std::size_t bytes) noexcept;
LIBIPC_EXPORT void free (void *p, std::size_t bytes) noexcept;
namespace detail_new {
#if defined(LIBIPC_CPP_17)
using recycle_t = void (*)(void *p) noexcept;
#else
using recycle_t = void (*)(void *p);
#endif
static constexpr std::size_t recycler_size = round_up(sizeof(recycle_t), alignof(std::size_t));
static constexpr std::size_t allocated_size = sizeof(std::size_t);
static constexpr std::size_t regular_head_size = round_up(recycler_size + allocated_size, alignof(std::max_align_t));
template <typename T>
struct do_allocate {
template <typename... A>
static T *apply(A &&... args) noexcept {
void *b = mem::alloc(regular_head_size + sizeof(T));
auto *p = static_cast<byte *>(b) + regular_head_size;
LIBIPC_TRY {
T *t = construct<T>(p, std::forward<A>(args)...);
*reinterpret_cast<recycle_t *>(b)
= [](void *p) noexcept {
mem::free(static_cast<byte *>(destroy(static_cast<T *>(p))) - regular_head_size
, regular_head_size + sizeof(T));
};
return t;
} LIBIPC_CATCH(...) {
return nullptr;
}
}
};
template <>
struct do_allocate<void> {
static void *apply(std::size_t bytes) noexcept {
if (bytes == 0) return nullptr;
std::size_t rbz = regular_head_size + bytes;
void *b = mem::alloc(rbz);
*reinterpret_cast<recycle_t *>(b)
= [](void *p) noexcept {
auto *b = static_cast<byte *>(p) - regular_head_size;
mem::free(b, *reinterpret_cast<std::size_t *>(b + recycler_size));
};
auto *z = static_cast<byte *>(b) + recycler_size;
*reinterpret_cast<std::size_t *>(z) = rbz;
return static_cast<byte *>(b) + regular_head_size;
}
};
} // namespace detail_new
/// \brief Creates an object based on the specified type and parameters with block pool resource.
/// \note This function is thread-safe.
template <typename T, typename... A>
T *$new(A &&... args) noexcept {
return detail_new::do_allocate<T>::apply(std::forward<A>(args)...);
}
/// \brief Destroys object previously allocated by the `$new` and releases obtained memory area.
/// \note This function is thread-safe. If the pointer type passed in is different from `$new`,
/// additional performance penalties may be incurred.
inline void $delete(void *p) noexcept {
if (p == nullptr) return;
auto *r = reinterpret_cast<detail_new::recycle_t *>(static_cast<byte *>(p) - detail_new::regular_head_size);
(*r)(p);
}
/// \brief The destruction policy used by std::unique_ptr.
/// \see https://en.cppreference.com/w/cpp/memory/default_delete
struct deleter {
template <typename T>
void operator()(T *p) const noexcept {
$delete(p);
}
};
} // namespace mem
} // namespace ipc

View File

@ -3,13 +3,13 @@
#include <cstdint> // std::uint64_t
#include <system_error>
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
namespace ipc {
namespace sync {
class IPC_EXPORT mutex {
class LIBIPC_EXPORT mutex {
mutex(mutex const &) = delete;
mutex &operator=(mutex const &) = delete;

View File

@ -1,103 +0,0 @@
#pragma once
#include <new>
#include <utility>
#include "libipc/export.h"
#include "libipc/def.h"
namespace ipc {
namespace mem {
class IPC_EXPORT pool_alloc {
public:
static void* alloc(std::size_t size) noexcept;
static void free (void* p, std::size_t size) noexcept;
};
////////////////////////////////////////////////////////////////
/// construct/destruct an object
////////////////////////////////////////////////////////////////
namespace detail {
template <typename T>
struct impl {
template <typename... P>
static T* construct(T* p, P&&... params) {
::new (p) T(std::forward<P>(params)...);
return p;
}
static void destruct(T* p) {
reinterpret_cast<T*>(p)->~T();
}
};
template <typename T, size_t N>
struct impl<T[N]> {
using type = T[N];
template <typename... P>
static type* construct(type* p, P&&... params) {
for (size_t i = 0; i < N; ++i) {
impl<T>::construct(&((*p)[i]), std::forward<P>(params)...);
}
return p;
}
static void destruct(type* p) {
for (size_t i = 0; i < N; ++i) {
impl<T>::destruct(&((*p)[i]));
}
}
};
} // namespace detail
template <typename T, typename... P>
T* construct(T* p, P&&... params) {
return detail::impl<T>::construct(p, std::forward<P>(params)...);
}
template <typename T, typename... P>
T* construct(void* p, P&&... params) {
return construct(static_cast<T*>(p), std::forward<P>(params)...);
}
template <typename T>
void destruct(T* p) {
return detail::impl<T>::destruct(p);
}
template <typename T>
void destruct(void* p) {
destruct(static_cast<T*>(p));
}
////////////////////////////////////////////////////////////////
/// general alloc/free
////////////////////////////////////////////////////////////////
inline void* alloc(std::size_t size) {
return pool_alloc::alloc(size);
}
template <typename T, typename... P>
T* alloc(P&&... params) {
return construct<T>(pool_alloc::alloc(sizeof(T)), std::forward<P>(params)...);
}
inline void free(void* p, std::size_t size) {
pool_alloc::free(p, size);
}
template <typename T>
void free(T* p) {
if (p == nullptr) return;
destruct(p);
pool_alloc::free(p, sizeof(T));
}
} // namespace mem
} // namespace ipc

View File

@ -2,13 +2,13 @@
#include <cstdint> // std::uint64_t
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
namespace ipc {
namespace sync {
class IPC_EXPORT semaphore {
class LIBIPC_EXPORT semaphore {
semaphore(semaphore const &) = delete;
semaphore &operator=(semaphore const &) = delete;

View File

@ -3,7 +3,7 @@
#include <cstddef>
#include <cstdint>
#include "libipc/export.h"
#include "libipc/imp/export.h"
namespace ipc {
namespace shm {
@ -15,16 +15,16 @@ enum : unsigned {
open = 0x02
};
IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
IPC_EXPORT std::int32_t release(id_t id) noexcept;
IPC_EXPORT void remove (id_t id) noexcept;
IPC_EXPORT void remove (char const * name) noexcept;
LIBIPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
LIBIPC_EXPORT void * get_mem(id_t id, std::size_t * size);
LIBIPC_EXPORT std::int32_t release(id_t id) noexcept;
LIBIPC_EXPORT void remove (id_t id) noexcept;
LIBIPC_EXPORT void remove (char const * name) noexcept;
IPC_EXPORT std::int32_t get_ref(id_t id);
IPC_EXPORT void sub_ref(id_t id);
LIBIPC_EXPORT std::int32_t get_ref(id_t id);
LIBIPC_EXPORT void sub_ref(id_t id);
class IPC_EXPORT handle {
class LIBIPC_EXPORT handle {
public:
handle();
handle(char const * name, std::size_t size, unsigned mode = create | open);

View File

@ -5,6 +5,8 @@ set (PACKAGE_VERSION 1.3.0)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/sync SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/platform SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/imp SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/mem SRC_FILES)
file(GLOB HEAD_FILES
${LIBIPC_PROJECT_DIR}/include/libipc/*.h

View File

@ -33,7 +33,7 @@ public:
void init() {
/* DCLP */
if (!constructed_.load(std::memory_order_acquire)) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lc_);
LIBIPC_UNUSED auto guard = ipc::detail::unique_lock(lc_);
if (!constructed_.load(std::memory_order_relaxed)) {
::new (this) conn_head_base;
constructed_.store(true, std::memory_order_release);

365
src/libipc/imp/codecvt.cpp Normal file
View File

@ -0,0 +1,365 @@
#include <algorithm>
#include <type_traits>
#include <cstring>
#include <cstdint>
#include "libipc/imp/codecvt.h"
#include "libipc/imp/detect_plat.h"
#if defined(LIBIPC_OS_WIN)
# include "libipc/platform/win/codecvt.h"
#endif
namespace ipc {
/**
* \brief The transform between local-character-set(UTF-8/GBK/...) and UTF-16/32.
*
* Modified from UnicodeConverter.
* Copyright (c) 2010. Jianhui Qin (http://blog.csdn.net/jhqin).
*
* \remarks codecvt_utf8_utf16/std::wstring_convert is deprecated.
* \see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/
* https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement
* https://en.cppreference.com/w/cpp/locale/codecvt/in
*/
namespace {
/// \brief X-bit unicode transformation format
enum class ufmt {
utf8,
utf16,
utf32,
};
template <typename T, ufmt, typename = void>
struct utf_compatible : std::false_type {};
template <typename T>
struct utf_compatible<T, ufmt::utf8,
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 1)>> : std::true_type {};
template <typename T>
struct utf_compatible<T, ufmt::utf16,
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 2)>> : std::true_type {};
template <typename T>
struct utf_compatible<T, ufmt::utf32,
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 4)>> : std::true_type {};
template <typename T, ufmt Fmt>
constexpr bool utf_compatible_v = utf_compatible<T, Fmt>::value;
/**
* \brief UTF-32 --> UTF-8
*/
template <typename T, typename U>
auto cvt_char(T src, U* des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
utf_compatible_v<U, ufmt::utf8>, std::size_t> {
if (src == 0) return 0;
constexpr std::uint8_t prefix[] = {
0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC
};
constexpr std::uint32_t codeup[] = {
0x80, // U+00000000 - U+0000007F
0x800, // U+00000080 - U+000007FF
0x10000, // U+00000800 - U+0000FFFF
0x200000, // U+00010000 - U+001FFFFF
0x4000000, // U+00200000 - U+03FFFFFF
0x80000000 // U+04000000 - U+7FFFFFFF
};
std::size_t i, len = sizeof(codeup) / sizeof(std::uint32_t);
for(i = 0; i < len; ++i) {
if (static_cast<std::uint32_t>(src) < codeup[i]) break;
}
if (i == len) return 0; // the src is invalid
len = i + 1;
if (des != nullptr) {
if (dlen > i) for (; i > 0; --i) {
des[i] = static_cast<U>((src & 0x3F) | 0x80);
src >>= 6;
}
des[0] = static_cast<U>(src | prefix[len - 1]);
}
return len;
}
/**
* \brief UTF-8 --> UTF-32
*/
template <typename T, typename U>
auto cvt_char(T const *src, std::size_t slen, U &des) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf8> &&
utf_compatible_v<U, ufmt::utf32>, std::size_t> {
if ((src == nullptr) || (*src) == 0) return 0;
if (slen == 0) return 0;
std::uint8_t b = (std::uint8_t)*(src++);
if (b < 0x80) {
des = b;
return 1;
}
if (b < 0xC0 || b > 0xFD) return 0; // the src is invalid
std::size_t len;
if (b < 0xE0) {
des = b & 0x1F;
len = 2;
} else if (b < 0xF0) {
des = b & 0x0F;
len = 3;
} else if (b < 0xF8) {
des = b & 0x07;
len = 4;
} else if (b < 0xFC) {
des = b & 0x03;
len = 5;
} else {
des = b & 0x01;
len = 6;
}
if (slen < len) return 0;
std::size_t i = 1;
for(; i < len; ++i) {
b = *(src++);
if ((b < 0x80) || (b > 0xBF)) return 0; // the src is invalid
des = (des << 6) + (b & 0x3F);
}
return len;
}
/**
* \brief UTF-32 --> UTF-16
*/
template <typename T, typename U>
auto cvt_char(T src, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
utf_compatible_v<U, ufmt::utf16>, std::size_t> {
if (src == 0) return 0;
if (src <= 0xFFFF) {
if ((des != nullptr) && (dlen != 0)) {
(*des) = static_cast<U>(src);
}
return 1;
} else if (src <= 0xEFFFF) {
if ((des != nullptr) && (dlen > 1)) {
des[0] = static_cast<U>(0xD800 + (src >> 10) - 0x40); // high
des[1] = static_cast<U>(0xDC00 + (src & 0x03FF)); // low
}
return 2;
}
return 0;
}
/**
* \brief UTF-16 --> UTF-32
*/
template <typename T, typename U>
auto cvt_char(T const *src, std::size_t slen, U &des)
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf16> &&
utf_compatible_v<U, ufmt::utf32>, std::size_t> {
if ((src == nullptr) || (*src) == 0) return 0;
if (slen == 0) return 0;
std::uint16_t w1 = src[0];
if ((w1 >= 0xD800) && (w1 <= 0xDFFF)) {
if (w1 < 0xDC00) {
if (slen < 2) return 0;
std::uint16_t w2 = src[1];
if ((w2 >= 0xDC00) && (w2 <= 0xDFFF)) {
des = (w2 & 0x03FF) + (((w1 & 0x03FF) + 0x40) << 10);
return 2;
}
}
return 0; // the src is invalid
}
des = w1;
return 1;
}
/**
* \brief UTF-16 --> UTF-8
*/
template <typename T, typename U>
auto cvt_char(T src, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf16> &&
utf_compatible_v<U, ufmt::utf8>, std::size_t> {
// make utf-16 to utf-32
std::uint32_t tmp;
if (cvt_char(&src, 1, tmp) != 1) return 0;
// make utf-32 to utf-8
return cvt_char(tmp, des, dlen);
}
/**
* \brief UTF-8 --> UTF-16
*/
template <typename T, typename U>
auto cvt_char(T const *src, std::size_t slen, U &des)
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf8> &&
utf_compatible_v<U, ufmt::utf16>, std::size_t> {
// make utf-8 to utf-32
std::uint32_t tmp;
std::size_t len = cvt_char(src, slen, tmp);
if (len == 0) return 0;
// make utf-32 to utf-16
if (cvt_char(tmp, &des, 1) != 1) return 0;
return len;
}
/**
* \brief UTF-32 string --> UTF-8/16 string
*/
template <typename T, typename U>
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
(utf_compatible_v<U, ufmt::utf16> || utf_compatible_v<U, ufmt::utf8>), std::size_t> {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
std::size_t num = 0, len = 0;
for (std::size_t i = 0; (i < slen) && ((*src) != 0); ++src, ++i) {
len = cvt_char(*src, des, dlen);
if (len == 0) return 0;
if (des != nullptr) {
des += len;
if (dlen < len) {
dlen = 0;
} else {
dlen -= len;
}
}
num += len;
}
return num;
}
/**
* \brief UTF-8/16 string --> UTF-32 string
*/
template <typename T, typename U>
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<U, ufmt::utf32> &&
(utf_compatible_v<T, ufmt::utf16> || utf_compatible_v<T, ufmt::utf8>), std::size_t> {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
std::size_t num = 0;
for (std::size_t i = 0; (i < slen) && ((*src) != 0);) {
std::uint32_t tmp;
std::size_t len = cvt_char(src, slen - i, tmp);
if (len == 0) return 0;
if ((des != nullptr) && (dlen > 0)) {
(*des) = tmp;
++des;
dlen -= 1;
}
src += len;
i += len;
num += 1;
}
return num;
}
/**
* \brief UTF-8/16 string --> UTF-16/8 string
*/
template <typename T, typename U>
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<(utf_compatible_v<T, ufmt::utf8> && utf_compatible_v<U, ufmt::utf16>) ||
(utf_compatible_v<T, ufmt::utf16> && utf_compatible_v<U, ufmt::utf8>), std::size_t> {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
std::size_t num = 0;
for (std::size_t i = 0; (i < slen) && ((*src) != 0);) {
// make utf-x to utf-32
std::uint32_t tmp;
std::size_t len = cvt_char(src, slen - i, tmp);
if (len == 0) return 0;
src += len;
i += len;
// make utf-32 to utf-y
len = cvt_char(tmp, des, dlen);
if (len == 0) return 0;
if (des != nullptr) {
des += len;
if (dlen < len) {
dlen = 0;
} else {
dlen -= len;
}
}
num += len;
}
return num;
}
template <typename T, typename U>
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<(sizeof(T) == sizeof(U)), std::size_t> {
if ((des == nullptr) || (dlen == 0)) {
return slen;
}
std::size_t r = (std::min)(slen, dlen);
std::memcpy(des, src, r * sizeof(T));
return r;
}
} // namespace
#define LIBIPC_DEF_CVT_CSTR_($CHAR_T, $CHAR_U) \
template <> \
std::size_t cvt_cstr($CHAR_T const *src, std::size_t slen, $CHAR_U *des, std::size_t dlen) noexcept { \
return cvt_cstr_utf(src, slen, des, dlen); \
}
// #define LIBIPC_DEF_CVT_CSTR_($CHAR_T, $CHAR_U)
LIBIPC_DEF_CVT_CSTR_(char , char)
LIBIPC_DEF_CVT_CSTR_(char , char16_t)
LIBIPC_DEF_CVT_CSTR_(char , char32_t)
LIBIPC_DEF_CVT_CSTR_(wchar_t , wchar_t)
LIBIPC_DEF_CVT_CSTR_(char16_t, char16_t)
LIBIPC_DEF_CVT_CSTR_(char16_t, char)
LIBIPC_DEF_CVT_CSTR_(char16_t, char32_t)
LIBIPC_DEF_CVT_CSTR_(char32_t, char32_t)
LIBIPC_DEF_CVT_CSTR_(char32_t, char)
LIBIPC_DEF_CVT_CSTR_(char32_t, char16_t)
#if !defined(LIBIPC_OS_WIN)
LIBIPC_DEF_CVT_CSTR_(char , wchar_t)
LIBIPC_DEF_CVT_CSTR_(wchar_t , char)
LIBIPC_DEF_CVT_CSTR_(wchar_t , char16_t)
LIBIPC_DEF_CVT_CSTR_(wchar_t , char32_t)
LIBIPC_DEF_CVT_CSTR_(char16_t, wchar_t)
LIBIPC_DEF_CVT_CSTR_(char32_t, wchar_t)
#endif // !defined(LIBIPC_OS_WIN)
#if defined(LIBIPC_CPP_20)
LIBIPC_DEF_CVT_CSTR_(char8_t , char8_t)
LIBIPC_DEF_CVT_CSTR_(char8_t , char)
LIBIPC_DEF_CVT_CSTR_(char8_t , char16_t)
LIBIPC_DEF_CVT_CSTR_(char8_t , char32_t)
LIBIPC_DEF_CVT_CSTR_(char , char8_t)
LIBIPC_DEF_CVT_CSTR_(char16_t, char8_t)
LIBIPC_DEF_CVT_CSTR_(char32_t, char8_t)
#if !defined(LIBIPC_OS_WIN)
LIBIPC_DEF_CVT_CSTR_(char8_t , wchar_t)
LIBIPC_DEF_CVT_CSTR_(wchar_t , char8_t)
#endif // !defined(LIBIPC_OS_WIN)
#endif // defined(LIBIPC_CPP_20)
#undef LIBIPC_DEF_CVT_CSTR_
} // namespace ipc

329
src/libipc/imp/fmt.cpp Normal file
View File

@ -0,0 +1,329 @@
#include <cstdio> // std::snprintf
#include <iomanip> // std::put_time
#include <sstream> // std::ostringstream
#include <array>
#include <cstring> // std::memcpy
#include <algorithm> // std::min
#include <initializer_list>
#include <cstdint>
#include "libipc/imp/fmt.h"
#include "libipc/imp/codecvt.h"
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \brief Format conversions helpers.
* \see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html
* https://en.cppreference.com/w/cpp/io/c/fprintf
*/
namespace {
struct sfmt_policy {
static constexpr std::size_t aligned_size = 32U;
};
template <typename Policy = sfmt_policy>
span<char> local_fmt_sbuf() noexcept {
thread_local std::array<char, Policy::aligned_size> sbuf;
return sbuf;
}
span<char const> normalize(span<char const> const &a) {
if (a.empty()) return {};
return a.first(a.size() - (a.back() == '\0' ? 1 : 0));
}
span<char> smem_cpy(span<char> const &sbuf, span<char const> a) noexcept {
if (sbuf.empty()) return {};
a = normalize(a);
auto sz = (std::min)(sbuf.size() - 1, a.size());
if (sz != 0) std::memcpy(sbuf.data(), a.data(), sz);
return sbuf.first(sz);
}
span<char> sbuf_cpy(span<char> sbuf, span<char const> const &a) noexcept {
sbuf = smem_cpy(sbuf, a);
*sbuf.end() = '\0';
return sbuf;
}
span<char> sbuf_cat(span<char> const &sbuf, std::initializer_list<span<char const>> args) noexcept {
std::size_t remain = sbuf.size();
for (auto s : args) {
remain -= smem_cpy(sbuf.last(remain), s).size();
}
auto sz = sbuf.size() - remain;
sbuf[sz] = '\0';
return sbuf.first(sz);
}
char const *as_cstr(span<char const> const &a) {
if (a.empty()) return "";
if (a.back() == '\0') return a.data();
return sbuf_cpy(local_fmt_sbuf(), a).data();
}
span<char> fmt_of(span<char const> const &fstr, span<char const> const &s) {
return sbuf_cat(local_fmt_sbuf(), {"%", fstr, s});
}
span<char> fmt_of_unsigned(span<char const> fstr, span<char const> const &l) {
if (fstr.empty()) {
return fmt_of(l, "u");
}
fstr = normalize(fstr);
switch (fstr.back()) {
case 'o':
case 'x':
case 'X':
case 'u': return sbuf_cat(local_fmt_sbuf(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)});
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "u"});
}
}
span<char> fmt_of_signed(span<char const> fstr, span<char const> const &l) {
if (fstr.empty()) {
return fmt_of(l, "d");
}
fstr = normalize(fstr);
switch (fstr.back()) {
case 'o':
case 'x':
case 'X':
case 'u': return fmt_of_unsigned(fstr, l);
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "d"});
}
}
span<char> fmt_of_float(span<char const> fstr, span<char const> const &l) {
if (fstr.empty()) {
return fmt_of(l, "f");
}
fstr = normalize(fstr);
switch (fstr.back()) {
case 'e':
case 'E':
case 'g':
case 'G': return sbuf_cat(local_fmt_sbuf(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)});
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "f"});
}
}
template <typename A /*a fundamental or pointer type*/>
int sprintf(fmt_context &ctx, span<char const> const &sfmt, A a) {
for (std::int32_t sz = -1;;) {
auto sbuf = ctx.buffer(sz + 1);
if (sbuf.size() < std::size_t(sz + 1)) {
return -1;
}
sz = std::snprintf(sbuf.data(), sbuf.size(), sfmt.data(), a);
if (sz <= 0) {
return sz;
}
if (std::size_t(sz) < sbuf.size()) {
ctx.expend(sz);
return sz;
}
}
}
template <typename F /*a function pointer*/,
typename A /*a fundamental or pointer type*/>
bool sprintf(fmt_context &ctx, F fop, span<char const> const &fstr, span<char const> const &s, A a) noexcept {
LIBIPC_TRY {
return ipc::sprintf(ctx, fop(fstr, s), a) >= 0;
} LIBIPC_CATCH(...) {
return false;
}
}
} // namespace
/// \brief The context of fmt.
fmt_context::fmt_context(std::string &j) noexcept
: joined_(j)
, offset_(0) {}
std::size_t fmt_context::capacity() noexcept {
return (offset_ < sbuf_.size()) ? sbuf_.size() : joined_.size();
}
void fmt_context::reset() noexcept {
offset_ = 0;
}
bool fmt_context::finish() noexcept {
LIBIPC_TRY {
if (offset_ < sbuf_.size()) {
joined_.assign(sbuf_.data(), offset_);
} else {
joined_.resize(offset_);
}
return true;
} LIBIPC_CATCH(...) {
return false;
}
}
span<char> fmt_context::buffer(std::size_t sz) noexcept {
auto roundup = [](std::size_t sz) noexcept {
constexpr std::size_t fmt_context_aligned_size = 512U;
return (sz & ~(fmt_context_aligned_size - 1)) + fmt_context_aligned_size;
};
auto sbuf = make_span(sbuf_);
LIBIPC_TRY {
if (offset_ < sbuf.size()) {
if ((offset_ + sz) < sbuf.size()) {
return sbuf.subspan(offset_);
} else {
/// \remark switch the cache to std::string
joined_.assign(sbuf.data(), offset_);
joined_.resize(roundup(offset_ + sz));
}
} else if ((offset_ + sz) >= joined_.size()) {
joined_.resize(roundup(offset_ + sz));
}
return {&joined_[offset_], joined_.size() - offset_};
} LIBIPC_CATCH(...) {
return {};
}
}
void fmt_context::expend(std::size_t sz) noexcept {
offset_ += sz;
}
bool fmt_context::append(span<char const> const &str) noexcept {
auto sz = str.size();
if (sz == 0) return true;
if (str.back() == '\0') --sz;
auto sbuf = buffer(sz);
if (sbuf.size() < sz) {
return false;
}
std::memcpy(sbuf.data(), str.data(), sz);
offset_ += sz;
return true;
}
/// \brief To string conversion.
bool to_string(fmt_context &ctx, char const *a) noexcept {
return to_string(ctx, a, {});
}
bool to_string(fmt_context &ctx, std::string const &a) noexcept {
return ctx.append(a);
}
bool to_string(fmt_context &ctx, char const *a, span<char const> fstr) noexcept {
if (a == nullptr) {
return ipc::sprintf(ctx, fmt_of, fstr, "s", "");
} else {
return ipc::sprintf(ctx, fmt_of, fstr, "s", a);
}
}
bool to_string(fmt_context &ctx, char a) noexcept {
return ipc::sprintf(ctx, fmt_of, {}, "c", a);
}
bool to_string(fmt_context &ctx, wchar_t a) noexcept {
LIBIPC_TRY {
std::string des;
cvt_sstr(std::wstring{a}, des);
return ctx.append(des);
} LIBIPC_CATCH(...) {
return false;
}
}
bool to_string(fmt_context &ctx, char16_t a) noexcept {
LIBIPC_TRY {
std::string des;
cvt_sstr(std::u16string{a}, des);
return ctx.append(des);
} LIBIPC_CATCH(...) {
return false;
}
}
bool to_string(fmt_context &ctx, char32_t a) noexcept {
LIBIPC_TRY {
std::string des;
cvt_sstr(std::u32string{a}, des);
return ctx.append(des);
} LIBIPC_CATCH(...) {
return false;
}
}
bool to_string(fmt_context &ctx, signed short a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_signed, fstr, "h", a);
}
bool to_string(fmt_context &ctx, unsigned short a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "h", a);
}
bool to_string(fmt_context &ctx, signed int a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_signed, fstr, "", a);
}
bool to_string(fmt_context &ctx, unsigned int a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "", a);
}
bool to_string(fmt_context &ctx, signed long a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_signed, fstr, "l", a);
}
bool to_string(fmt_context &ctx, unsigned long a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "l", a);
}
bool to_string(fmt_context &ctx, signed long long a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_signed, fstr, "ll", a);
}
bool to_string(fmt_context &ctx, unsigned long long a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "ll", a);
}
bool to_string(fmt_context &ctx, double a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_float, fstr, "", a);
}
bool to_string(fmt_context &ctx, long double a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_float, fstr, "L", a);
}
bool to_string(fmt_context &ctx, std::nullptr_t) noexcept {
return ctx.append("null");
}
bool to_string(fmt_context &ctx, void const volatile *a) noexcept {
if (a == nullptr) {
return to_string(ctx, nullptr);
}
return ipc::sprintf(ctx, fmt_of, "", "p", a);
}
bool to_string(fmt_context &ctx, std::tm const &a, span<char const> fstr) noexcept {
if (fstr.empty()) {
fstr = "%Y-%m-%d %H:%M:%S";
}
LIBIPC_TRY {
std::ostringstream ss;
ss << std::put_time(&a, as_cstr(fstr));
return ctx.append(ss.str());
} LIBIPC_CATCH(...) {
return {};
}
}
} // namespace ipc

View File

@ -0,0 +1,7 @@
#include "libipc/imp/detect_plat.h"
#if defined(LIBIPC_CC_GNUC)
# include "libipc/platform/gnuc/demangle.h"
#else
# include "libipc/platform/win/demangle.h"
#endif

View File

@ -0,0 +1,7 @@
#include "libipc/imp/detect_plat.h"
#if defined(LIBIPC_OS_WIN)
# include "libipc/platform/win/system.h"
#else
# include "libipc/platform/posix/system.h"
#endif

View File

@ -14,7 +14,6 @@
#include "libipc/ipc.h"
#include "libipc/def.h"
#include "libipc/shm.h"
#include "libipc/pool_alloc.h"
#include "libipc/queue.h"
#include "libipc/policy.h"
#include "libipc/rw_lock.h"
@ -25,7 +24,8 @@
#include "libipc/utility/scope_guard.h"
#include "libipc/utility/utility.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/mem/new.h"
#include "libipc/platform/detail.h"
#include "libipc/circ/elem_array.h"
@ -64,25 +64,30 @@ struct msg_t : msg_t<0, AlignSize> {
};
template <typename T>
ipc::buff_t make_cache(T& data, std::size_t size) {
auto ptr = ipc::mem::alloc(size);
ipc::buff_t make_cache(T &data, std::size_t size) {
auto *ptr = ipc::mem::$new<void>(size);
std::memcpy(ptr, &data, (ipc::detail::min)(sizeof(data), size));
return { ptr, size, ipc::mem::free };
return {
ptr, size,
[](void *p, std::size_t) noexcept {
ipc::mem::$delete(p);
}
};
}
acc_t *cc_acc(ipc::string const &pref) {
static ipc::unordered_map<ipc::string, ipc::shm::handle> handles;
acc_t *cc_acc(std::string const &pref) {
static auto *phs = new ipc::unordered_map<std::string, ipc::shm::handle>; // no delete
static std::mutex lock;
std::lock_guard<std::mutex> guard {lock};
auto it = handles.find(pref);
if (it == handles.end()) {
ipc::string shm_name {ipc::make_prefix(pref, {"CA_CONN__"})};
auto it = phs->find(pref);
if (it == phs->end()) {
std::string shm_name {ipc::make_prefix(pref, "CA_CONN__")};
ipc::shm::handle h;
if (!h.acquire(shm_name.c_str(), sizeof(acc_t))) {
ipc::error("[cc_acc] acquire failed: %s\n", shm_name.c_str());
return nullptr;
}
it = handles.emplace(pref, std::move(h)).first;
it = phs->emplace(pref, std::move(h)).first;
}
return static_cast<acc_t *>(it->second.get());
}
@ -105,8 +110,8 @@ struct cache_t {
struct conn_info_head {
ipc::string prefix_;
ipc::string name_;
std::string prefix_;
std::string name_;
msg_id_t cc_id_; // connection-info id
ipc::detail::waiter cc_waiter_, wt_waiter_, rd_waiter_;
ipc::shm::handle acc_h_;
@ -117,10 +122,10 @@ struct conn_info_head {
, cc_id_ {} {}
void init() {
if (!cc_waiter_.valid()) cc_waiter_.open(ipc::make_prefix(prefix_, {"CC_CONN__", name_}).c_str());
if (!wt_waiter_.valid()) wt_waiter_.open(ipc::make_prefix(prefix_, {"WT_CONN__", name_}).c_str());
if (!rd_waiter_.valid()) rd_waiter_.open(ipc::make_prefix(prefix_, {"RD_CONN__", name_}).c_str());
if (!acc_h_.valid()) acc_h_.acquire(ipc::make_prefix(prefix_, {"AC_CONN__", name_}).c_str(), sizeof(acc_t));
if (!cc_waiter_.valid()) cc_waiter_.open(ipc::make_prefix(prefix_, "CC_CONN__", name_).c_str());
if (!wt_waiter_.valid()) wt_waiter_.open(ipc::make_prefix(prefix_, "WT_CONN__", name_).c_str());
if (!rd_waiter_.valid()) rd_waiter_.open(ipc::make_prefix(prefix_, "RD_CONN__", name_).c_str());
if (!acc_h_.valid()) acc_h_.acquire(ipc::make_prefix(prefix_, "AC_CONN__", name_).c_str(), sizeof(acc_t));
if (cc_id_ != 0) {
return;
}
@ -146,10 +151,10 @@ struct conn_info_head {
static void clear_storage(char const * prefix, char const * name) noexcept {
auto p = ipc::make_string(prefix);
auto n = ipc::make_string(name);
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"CC_CONN__", n}).c_str());
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"WT_CONN__", n}).c_str());
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"RD_CONN__", n}).c_str());
ipc::shm::handle::clear_storage(ipc::make_prefix(p, {"AC_CONN__", n}).c_str());
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, "CC_CONN__", n).c_str());
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, "WT_CONN__", n).c_str());
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, "RD_CONN__", n).c_str());
ipc::shm::handle::clear_storage(ipc::make_prefix(p, "AC_CONN__", n).c_str());
}
void quit_waiting() {
@ -208,10 +213,10 @@ struct chunk_info_t {
auto& chunk_storages() {
class chunk_handle_t {
ipc::unordered_map<ipc::string, ipc::shm::handle> handles_;
ipc::unordered_map<std::string, ipc::shm::handle> handles_;
std::mutex lock_;
static bool make_handle(ipc::shm::handle &h, ipc::string const &shm_name, std::size_t chunk_size) {
static bool make_handle(ipc::shm::handle &h, std::string const &shm_name, std::size_t chunk_size) {
if (!h.valid() &&
!h.acquire( shm_name.c_str(),
sizeof(chunk_info_t) + chunk_info_t::chunks_mem_size(chunk_size) )) {
@ -223,8 +228,8 @@ auto& chunk_storages() {
public:
chunk_info_t *get_info(conn_info_head *inf, std::size_t chunk_size) {
ipc::string pref {(inf == nullptr) ? ipc::string{} : inf->prefix_};
ipc::string shm_name {ipc::make_prefix(pref, {"CHUNK_INFO__", ipc::to_string(chunk_size)})};
std::string pref {(inf == nullptr) ? std::string{} : inf->prefix_};
std::string shm_name {ipc::make_prefix(pref, "CHUNK_INFO__", chunk_size)};
ipc::shm::handle *h;
{
std::lock_guard<std::mutex> guard {lock_};
@ -243,8 +248,8 @@ auto& chunk_storages() {
};
using deleter_t = void (*)(chunk_handle_t*);
using chunk_handle_ptr_t = std::unique_ptr<chunk_handle_t, deleter_t>;
static ipc::map<std::size_t, chunk_handle_ptr_t> chunk_hs;
return chunk_hs;
static auto *chunk_hs = new ipc::map<std::size_t, chunk_handle_ptr_t>; // no delete
return *chunk_hs;
}
chunk_info_t *chunk_storage_info(conn_info_head *inf, std::size_t chunk_size) {
@ -252,15 +257,15 @@ chunk_info_t *chunk_storage_info(conn_info_head *inf, std::size_t chunk_size) {
std::decay_t<decltype(storages)>::iterator it;
{
static ipc::rw_lock lock;
IPC_UNUSED_ std::shared_lock<ipc::rw_lock> guard {lock};
LIBIPC_UNUSED std::shared_lock<ipc::rw_lock> guard {lock};
if ((it = storages.find(chunk_size)) == storages.end()) {
using chunk_handle_ptr_t = std::decay_t<decltype(storages)>::value_type::second_type;
using chunk_handle_t = chunk_handle_ptr_t::element_type;
guard.unlock();
IPC_UNUSED_ std::lock_guard<ipc::rw_lock> guard {lock};
LIBIPC_UNUSED std::lock_guard<ipc::rw_lock> guard {lock};
it = storages.emplace(chunk_size, chunk_handle_ptr_t{
ipc::mem::alloc<chunk_handle_t>(), [](chunk_handle_t *p) {
ipc::mem::destruct(p);
ipc::mem::$new<chunk_handle_t>(), [](chunk_handle_t *p) {
ipc::mem::$delete(p);
}}).first;
}
}
@ -394,11 +399,11 @@ struct queue_generator {
void init() {
conn_info_head::init();
if (!que_.valid()) {
que_.open(ipc::make_prefix(prefix_, {
que_.open(ipc::make_prefix(prefix_,
"QU_CONN__",
this->name_,
"__", ipc::to_string(DataSize),
"__", ipc::to_string(AlignSize)}).c_str());
"__", DataSize,
"__", AlignSize).c_str());
}
}
@ -408,11 +413,11 @@ struct queue_generator {
}
static void clear_storage(char const * prefix, char const * name) noexcept {
queue_t::clear_storage(ipc::make_prefix(ipc::make_string(prefix), {
queue_t::clear_storage(ipc::make_prefix(prefix,
"QU_CONN__",
ipc::make_string(name),
"__", ipc::to_string(DataSize),
"__", ipc::to_string(AlignSize)}).c_str());
name,
"__", DataSize,
"__", AlignSize).c_str());
conn_info_head::clear_storage(prefix, name);
}
@ -447,7 +452,7 @@ constexpr static queue_t* queue_of(ipc::handle_t h) noexcept {
static bool connect(ipc::handle_t * ph, ipc::prefix pref, char const * name, bool start_to_recv) {
assert(ph != nullptr);
if (*ph == nullptr) {
*ph = ipc::mem::alloc<conn_info_t>(pref.str, name);
*ph = ipc::mem::$new<conn_info_t>(pref.str, name);
}
return reconnect(ph, start_to_recv);
}
@ -490,7 +495,7 @@ static bool reconnect(ipc::handle_t * ph, bool start_to_recv) {
}
static void destroy(ipc::handle_t h) noexcept {
ipc::mem::free(info_of(h));
ipc::mem::$delete(info_of(h));
}
static std::size_t recv_count(ipc::handle_t h) noexcept {
@ -654,20 +659,20 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
conn_info_t * inf;
ipc::circ::cc_t curr_conns;
ipc::circ::cc_t conn_id;
} *r_info = ipc::mem::alloc<recycle_t>(recycle_t{
} *r_info = ipc::mem::$new<recycle_t>(recycle_t{
buf_id,
inf,
que->elems()->connections(std::memory_order_relaxed),
que->connected_id()
});
if (r_info == nullptr) {
ipc::log("fail: ipc::mem::alloc<recycle_t>.\n");
ipc::log("fail: ipc::mem::$new<recycle_t>.\n");
return ipc::buff_t{buf, msg_size}; // no recycle
} else {
return ipc::buff_t{buf, msg_size, [](void* p_info, std::size_t size) {
auto r_info = static_cast<recycle_t *>(p_info);
IPC_UNUSED_ auto finally = ipc::guard([r_info] {
ipc::mem::free(r_info);
LIBIPC_UNUSED auto finally = ipc::guard([r_info] {
ipc::mem::$delete(r_info);
});
recycle_storage<flag_t>(r_info->storage_id,
r_info->inf,

View File

@ -0,0 +1,53 @@
#include <algorithm> // std::swap
#include "libipc/imp/log.h"
#include "libipc/mem/bytes_allocator.h"
#include "libipc/mem/memory_resource.h"
namespace ipc {
namespace mem {
bytes_allocator::holder_mr_base &bytes_allocator::get_holder() noexcept {
return *reinterpret_cast<holder_mr_base *>(holder_.data());
}
bytes_allocator::holder_mr_base const &bytes_allocator::get_holder() const noexcept {
return *reinterpret_cast<holder_mr_base const *>(holder_.data());
}
void bytes_allocator::init_default_resource() noexcept {
std::ignore = ipc::construct<holder_mr<new_delete_resource>>(holder_.data(), new_delete_resource::get());
}
bytes_allocator::bytes_allocator() noexcept
: bytes_allocator(new_delete_resource::get()) {}
bytes_allocator::~bytes_allocator() noexcept {
ipc::destroy(&get_holder());
}
void bytes_allocator::swap(bytes_allocator &other) noexcept {
std::swap(this->holder_, other.holder_);
}
void *bytes_allocator::allocate(std::size_t s, std::size_t a) const {
LIBIPC_LOG();
if ((a & (a - 1)) != 0) {
log.error("failed: allocate alignment is not a power of 2.");
return nullptr;
}
return get_holder().alloc(s, a);
}
void bytes_allocator::deallocate(void *p, std::size_t s, std::size_t a) const {
LIBIPC_LOG();
if ((a & (a - 1)) != 0) {
log.error("failed: allocate alignment is not a power of 2.");
return;
}
get_holder().dealloc(p, s, a);
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,47 @@
#include <mutex>
#include <array>
#include <cstddef>
#include "libipc/def.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/byte.h"
#include "libipc/mem/bytes_allocator.h"
#include "libipc/mem/memory_resource.h"
namespace ipc {
namespace mem {
class thread_safe_resource : public monotonic_buffer_resource {
public:
thread_safe_resource(span<byte> buffer) noexcept
: monotonic_buffer_resource(buffer) {}
~thread_safe_resource() noexcept {
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
monotonic_buffer_resource::release();
}
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
return monotonic_buffer_resource::allocate(bytes, alignment);
}
void deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
monotonic_buffer_resource::deallocate(p, bytes, alignment);
}
private:
std::mutex mutex_;
};
bytes_allocator &central_cache_allocator() noexcept {
static std::array<byte, central_cache_default_size> buf;
static thread_safe_resource res(buf);
static bytes_allocator a(&res);
return a;
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,140 @@
#include <utility>
#include <memory>
#include <algorithm>
#include "libipc/imp/log.h"
#include "libipc/imp/aligned.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/mem/memory_resource.h"
namespace ipc {
namespace mem {
namespace {
template <typename Node>
Node *make_node(bytes_allocator const &upstream, std::size_t initial_size, std::size_t alignment) noexcept {
LIBIPC_LOG();
auto sz = ipc::round_up(sizeof(Node), alignment) + initial_size;
LIBIPC_TRY {
auto *node = static_cast<Node *>(upstream.allocate(sz));
if (node == nullptr) {
log.error("failed: allocate memory for `monotonic_buffer_resource`'s node.",
" bytes = ", initial_size, ", alignment = ", alignment);
return nullptr;
}
node->next = nullptr;
node->size = sz;
return node;
} LIBIPC_CATCH(...) {
log.error("failed: allocate memory for `monotonic_buffer_resource`'s node.",
" bytes = ", initial_size, ", alignment = ", alignment,
"\n\texception: ", ipc::log::exception_string(std::current_exception()));
return nullptr;
}
}
std::size_t next_buffer_size(std::size_t size) noexcept {
return size * 3 / 2;
}
} // namespace
monotonic_buffer_resource::monotonic_buffer_resource() noexcept
: monotonic_buffer_resource(bytes_allocator{}) {}
monotonic_buffer_resource::monotonic_buffer_resource(bytes_allocator upstream) noexcept
: monotonic_buffer_resource(0, std::move(upstream)) {}
monotonic_buffer_resource::monotonic_buffer_resource(std::size_t initial_size) noexcept
: monotonic_buffer_resource(initial_size, bytes_allocator{}) {}
monotonic_buffer_resource::monotonic_buffer_resource(std::size_t initial_size, bytes_allocator upstream) noexcept
: upstream_ (std::move(upstream))
, free_list_ (nullptr)
, head_ (nullptr)
, tail_ (nullptr)
, next_size_ (initial_size)
, initial_buffer_(nullptr)
, initial_size_ (initial_size) {}
monotonic_buffer_resource::monotonic_buffer_resource(ipc::span<ipc::byte> buffer) noexcept
: monotonic_buffer_resource(buffer, bytes_allocator{}) {}
monotonic_buffer_resource::monotonic_buffer_resource(ipc::span<ipc::byte> buffer, bytes_allocator upstream) noexcept
: upstream_ (std::move(upstream))
, free_list_ (nullptr)
, head_ (buffer.begin())
, tail_ (buffer.end())
, next_size_ (next_buffer_size(buffer.size()))
, initial_buffer_(buffer.begin())
, initial_size_ (buffer.size()) {}
monotonic_buffer_resource::~monotonic_buffer_resource() noexcept {
release();
}
bytes_allocator monotonic_buffer_resource::upstream_resource() const noexcept {
return upstream_;
}
void monotonic_buffer_resource::release() noexcept {
LIBIPC_LOG();
LIBIPC_TRY {
while (free_list_ != nullptr) {
auto *next = free_list_->next;
upstream_.deallocate(free_list_, free_list_->size);
free_list_ = next;
}
} LIBIPC_CATCH(...) {
log.error("failed: deallocate memory for `monotonic_buffer_resource`.",
"\n\texception: ", ipc::log::exception_string(std::current_exception()));
}
// reset to initial state at contruction
if ((head_ = initial_buffer_) != nullptr) {
tail_ = head_ + initial_size_;
next_size_ = next_buffer_size(initial_size_);
} else {
tail_ = nullptr;
next_size_ = initial_size_;
}
}
void *monotonic_buffer_resource::allocate(std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_LOG();
if (bytes == 0) {
log.error("failed: allocate bytes = 0.");
return nullptr;
}
void *p = head_;
auto s = static_cast<std::size_t>(tail_ - head_);
if (std::align(alignment, bytes, p, s) == nullptr) {
next_size_ = (std::max)(next_size_, bytes);
auto *node = make_node<monotonic_buffer_resource::node>(upstream_, next_size_, alignment);
if (node == nullptr) return nullptr;
node->next = free_list_;
free_list_ = node;
next_size_ = next_buffer_size(next_size_);
// try again
s = node->size - sizeof(monotonic_buffer_resource::node);
p = std::align(alignment, bytes, (p = node + 1), s);
if (p == nullptr) {
log.error("failed: allocate memory for `monotonic_buffer_resource`.",
" bytes = ", bytes, ", alignment = ", alignment);
return nullptr;
}
tail_ = static_cast<ipc::byte *>(p) + s;
}
head_ = static_cast<ipc::byte *>(p) + bytes;
return p;
}
void monotonic_buffer_resource::deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
static_cast<void>(p);
static_cast<void>(bytes);
static_cast<void>(alignment);
// Do nothing.
}
} // namespace mem
} // namespace ipc

121
src/libipc/mem/new.cpp Normal file
View File

@ -0,0 +1,121 @@
#include "libipc/mem/new.h"
namespace ipc {
namespace mem {
/// \brief Select the incremental level based on the size.
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
return (s <= 128 ) ? 0 :
(s <= 1024 ) ? 1 :
(s <= 8192 ) ? 2 :
(s <= 65536) ? 3 : 4;
}
/// \brief Use block pools to handle memory less than 64K.
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_resource_base : public block_pool<BlockSize, BlockPoolExpansion>
, public block_collector {
public:
void *allocate(std::size_t /*bytes*/) noexcept override {
return block_pool<BlockSize, BlockPoolExpansion>::allocate();
}
void deallocate(void *p, std::size_t /*bytes*/) noexcept override {
block_pool<BlockSize, BlockPoolExpansion>::deallocate(p);
}
};
/// \brief Use `new`/`delete` to handle memory larger than 64K.
template <>
class block_resource_base<0, 0> : public new_delete_resource
, public block_collector {
public:
void *allocate(std::size_t bytes) noexcept override {
return new_delete_resource::allocate(bytes);
}
void deallocate(void *p, std::size_t bytes) noexcept override {
new_delete_resource::deallocate(p, bytes);
}
};
/// \brief Defines block pool memory resource based on block pool.
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_pool_resource : public block_resource_base<BlockSize, BlockPoolExpansion> {
public:
static block_collector &get() noexcept {
thread_local block_pool_resource instance;
return instance;
}
};
/// \brief Matches the appropriate memory block resource based on a specified size.
block_collector &get_regular_resource(std::size_t s) noexcept {
std::size_t l = regular_level(s);
switch (l) {
case 0:
switch (round_up<std::size_t>(s, 16)) {
case 16 : return block_pool_resource<16 , 512>::get();
case 32 : return block_pool_resource<32 , 512>::get();
case 48 : return block_pool_resource<48 , 512>::get();
case 64 : return block_pool_resource<64 , 512>::get();
case 80 : return block_pool_resource<80 , 512>::get();
case 96 : return block_pool_resource<96 , 512>::get();
case 112: return block_pool_resource<112, 512>::get();
case 128: return block_pool_resource<128, 512>::get();
default : break;
}
break;
case 1:
switch (round_up<std::size_t>(s, 128)) {
case 256 : return block_pool_resource<256 , 256>::get();
case 384 : return block_pool_resource<384 , 256>::get();
case 512 : return block_pool_resource<512 , 256>::get();
case 640 : return block_pool_resource<640 , 256>::get();
case 768 : return block_pool_resource<768 , 256>::get();
case 896 : return block_pool_resource<896 , 256>::get();
case 1024: return block_pool_resource<1024, 256>::get();
default : break;
}
break;
case 2:
switch (round_up<std::size_t>(s, 1024)) {
case 2048: return block_pool_resource<2048, 128>::get();
case 3072: return block_pool_resource<3072, 128>::get();
case 4096: return block_pool_resource<4096, 128>::get();
case 5120: return block_pool_resource<5120, 128>::get();
case 6144: return block_pool_resource<6144, 128>::get();
case 7168: return block_pool_resource<7168, 128>::get();
case 8192: return block_pool_resource<8192, 128>::get();
default : break;
}
break;
case 3:
switch (round_up<std::size_t>(s, 8192)) {
case 16384: return block_pool_resource<16384, 64>::get();
case 24576: return block_pool_resource<24576, 64>::get();
case 32768: return block_pool_resource<32768, 64>::get();
case 40960: return block_pool_resource<40960, 64>::get();
case 49152: return block_pool_resource<49152, 64>::get();
case 57344: return block_pool_resource<57344, 64>::get();
case 65536: return block_pool_resource<65536, 64>::get();
default : break;
}
break;
default:
break;
}
return block_pool_resource<0, 0>::get();
}
void *alloc(std::size_t bytes) noexcept {
return get_regular_resource(bytes).allocate(bytes);
}
void free(void *p, std::size_t bytes) noexcept {
return get_regular_resource(bytes).deallocate(p, bytes);
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,101 @@
#include <cstdlib> // std::aligned_alloc
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/aligned.h"
#include "libipc/imp/system.h"
#include "libipc/imp/log.h"
#include "libipc/mem/memory_resource.h"
#include "libipc/mem/verify_args.h"
namespace ipc {
namespace mem {
/**
* \brief Returns a pointer to a new_delete_resource.
*
* \return new_delete_resource*
*/
new_delete_resource *new_delete_resource::get() noexcept {
static new_delete_resource mem_res;
return &mem_res;
}
/**
* \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.
* Alignment shall be a power of two.
*
* \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate
* https://www.cppstories.com/2019/08/newnew-align/
*
* \return void * - nullptr if storage of the requested size and alignment cannot be obtained.
*/
void *new_delete_resource::allocate(std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_LOG();
if (!verify_args(bytes, alignment)) {
log.error("invalid bytes = ", bytes, ", alignment = ", alignment);
return nullptr;
}
#if defined(LIBIPC_CPP_17)
/// \see https://en.cppreference.com/w/cpp/memory/c/aligned_alloc
/// \remark The size parameter must be an integral multiple of alignment.
return std::aligned_alloc(alignment, ipc::round_up(bytes, alignment));
#else
if (alignment <= alignof(std::max_align_t)) {
/// \see https://en.cppreference.com/w/cpp/memory/c/malloc
return std::malloc(bytes);
}
#if defined(LIBIPC_OS_WIN)
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc
return ::_aligned_malloc(bytes, alignment);
#else // try posix
/// \see https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_memalign.html
void *p = nullptr;
int ret = ::posix_memalign(&p, alignment, bytes);
if (ret != 0) {
log.error("failed: posix_memalign(alignment = ", alignment,
", bytes = ", bytes,
"). error = ", sys::error(ret));
return nullptr;
}
return p;
#endif
#endif // defined(LIBIPC_CPP_17)
}
/**
* \brief Deallocates the storage pointed to by p.
* The storage it points to must not yet have been deallocated, otherwise the behavior is undefined.
*
* \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_deallocate
*
* \param p must have been returned by a prior call to new_delete_resource::do_allocate(bytes, alignment).
*/
void new_delete_resource::deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_LOG();
if (p == nullptr) {
return;
}
if (!verify_args(bytes, alignment)) {
log.error("invalid bytes = ", bytes, ", alignment = ", alignment);
return;
}
#if defined(LIBIPC_CPP_17)
/// \see https://en.cppreference.com/w/cpp/memory/c/free
std::free(p);
#else
if (alignment <= alignof(std::max_align_t)) {
std::free(p);
return;
}
#if defined(LIBIPC_OS_WIN)
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-free
::_aligned_free(p);
#else // try posix
::free(p);
#endif
#endif // defined(LIBIPC_CPP_17)
}
} // namespace mem
} // namespace ipc

39
src/libipc/mem/resource.h Executable file
View File

@ -0,0 +1,39 @@
#pragma once
#include <unordered_map>
#include <map>
#include <string>
#include "libipc/def.h"
#include "libipc/imp/fmt.h"
#include "libipc/mem/container_allocator.h"
namespace ipc {
template <typename Key, typename T>
using unordered_map = std::unordered_map<
Key, T, std::hash<Key>, std::equal_to<Key>, ipc::mem::container_allocator<std::pair<Key const, T>>
>;
template <typename Key, typename T>
using map = std::map<
Key, T, std::less<Key>, ipc::mem::container_allocator<std::pair<Key const, T>>
>;
/// \brief Check string validity.
constexpr bool is_valid_string(char const *str) noexcept {
return (str != nullptr) && (str[0] != '\0');
}
/// \brief Make a valid string.
inline std::string make_string(char const *str) {
return is_valid_string(str) ? std::string{str} : std::string{};
}
/// \brief Combine prefix from a list of strings.
template <typename A1, typename... A>
inline std::string make_prefix(A1 &&prefix, A &&...args) {
return ipc::fmt(std::forward<A1>(prefix), "__IPC_SHM__", std::forward<A>(args)...);
}
} // namespace ipc

View File

@ -0,0 +1,16 @@
#pragma once
#include <cstddef>
namespace ipc {
namespace mem {
/**
* \brief Check that bytes is not 0 and that the alignment is a power of two.
*/
inline constexpr bool verify_args(std::size_t bytes, std::size_t alignment) noexcept {
return (bytes > 0) && (alignment > 0) && ((alignment & (alignment - 1)) == 0);
}
} // namespace mem
} // namespace ipc

View File

@ -1,424 +0,0 @@
#pragma once
#include <algorithm>
#include <utility>
#include <iterator>
#include <limits> // std::numeric_limits
#include <cstdlib>
#include <cassert> // assert
#include "libipc/def.h"
#include "libipc/rw_lock.h"
#include "libipc/utility/concept.h"
#include "libipc/memory/allocator_wrapper.h"
#include "libipc/platform/detail.h"
namespace ipc {
namespace mem {
class static_alloc {
public:
static void swap(static_alloc&) noexcept {}
static void* alloc(std::size_t size) noexcept {
return size ? std::malloc(size) : nullptr;
}
static void free(void* p) noexcept {
std::free(p);
}
static void free(void* p, std::size_t /*size*/) noexcept {
free(p);
}
};
////////////////////////////////////////////////////////////////
/// Scope allocation -- The destructor will release all allocated blocks.
////////////////////////////////////////////////////////////////
namespace detail {
constexpr std::size_t aligned(std::size_t size, size_t alignment) noexcept {
return ( (size - 1) & ~(alignment - 1) ) + alignment;
}
IPC_CONCEPT_(has_take, take(std::move(std::declval<Type>())));
class scope_alloc_base {
protected:
struct block_t {
std::size_t size_;
block_t * next_;
} * head_ = nullptr, * tail_ = nullptr;
enum : std::size_t {
aligned_block_size = aligned(sizeof(block_t), alignof(std::max_align_t))
};
public:
void swap(scope_alloc_base & rhs) {
std::swap(head_, rhs.head_);
std::swap(tail_, rhs.tail_);
}
bool empty() const noexcept {
return head_ == nullptr;
}
void take(scope_alloc_base && rhs) {
if (rhs.empty()) return;
if (empty()) swap(rhs);
else {
std::swap(tail_->next_, rhs.head_);
// rhs.head_ should be nullptr here
tail_ = rhs.tail_;
rhs.tail_ = nullptr;
}
}
void free(void* /*p*/) {}
void free(void* /*p*/, std::size_t) {}
};
} // namespace detail
template <typename AllocP = static_alloc>
class scope_alloc : public detail::scope_alloc_base {
public:
using base_t = detail::scope_alloc_base;
using alloc_policy = AllocP;
private:
alloc_policy alloc_;
void free_all() {
while (!empty()) {
auto curr = head_;
head_ = head_->next_;
alloc_.free(curr, curr->size_);
}
// now head_ is nullptr
}
public:
scope_alloc() = default;
scope_alloc(scope_alloc && rhs) { swap(rhs); }
scope_alloc& operator=(scope_alloc rhs) { swap(rhs); return (*this); }
~scope_alloc() { free_all(); }
void swap(scope_alloc& rhs) {
alloc_.swap(rhs.alloc_);
base_t::swap(rhs);
}
template <typename A = AllocP>
auto take(scope_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
base_t::take(std::move(rhs));
alloc_.take(std::move(rhs.alloc_));
}
template <typename A = AllocP>
auto take(scope_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
base_t::take(std::move(rhs));
}
void* alloc(std::size_t size) {
std::size_t real_size = aligned_block_size + size;
auto curr = static_cast<block_t*>(alloc_.alloc(real_size));
curr->size_ = real_size;
curr->next_ = head_;
head_ = curr;
if (tail_ == nullptr) {
tail_ = curr;
}
return (reinterpret_cast<byte_t*>(curr) + aligned_block_size);
}
};
////////////////////////////////////////////////////////////////
/// Fixed-size blocks allocation
////////////////////////////////////////////////////////////////
namespace detail {
class fixed_alloc_base {
protected:
std::size_t block_size_;
std::size_t init_expand_;
void * cursor_;
void init(std::size_t block_size, std::size_t init_expand) {
block_size_ = block_size;
init_expand_ = init_expand;
cursor_ = nullptr;
}
static void** node_p(void* node) {
return reinterpret_cast<void**>(node);
}
static auto& next(void* node) {
return *node_p(node);
}
public:
bool operator<(fixed_alloc_base const & right) const {
return init_expand_ < right.init_expand_;
}
void set_block_size(std::size_t block_size) {
block_size_ = block_size;
}
void swap(fixed_alloc_base& rhs) {
std::swap(block_size_ , rhs.block_size_);
std::swap(init_expand_, rhs.init_expand_);
std::swap(cursor_ , rhs.cursor_);
}
bool empty() const noexcept {
return cursor_ == nullptr;
}
void take(fixed_alloc_base && rhs) {
assert(block_size_ == rhs.block_size_);
init_expand_ = (ipc::detail::max)(init_expand_, rhs.init_expand_);
if (rhs.empty()) return;
auto curr = cursor_;
if (curr != nullptr) while (1) {
auto next_cur = next(curr);
if (next_cur == nullptr) {
std::swap(next(curr), rhs.cursor_);
return;
}
// next_cur != nullptr
else curr = next_cur;
}
// curr == nullptr, means cursor_ == nullptr
else std::swap(cursor_, rhs.cursor_);
// rhs.cursor_ must be nullptr
}
void free(void* p) {
if (p == nullptr) return;
next(p) = cursor_;
cursor_ = p;
}
void free(void* p, std::size_t) {
free(p);
}
};
template <typename AllocP, typename ExpandP>
class fixed_alloc : public detail::fixed_alloc_base {
public:
using base_t = detail::fixed_alloc_base;
using alloc_policy = AllocP;
private:
alloc_policy alloc_;
void* try_expand() {
if (empty()) {
auto size = ExpandP::next(block_size_, init_expand_);
auto p = node_p(cursor_ = alloc_.alloc(size));
for (std::size_t i = 0; i < (size / block_size_) - 1; ++i)
p = node_p((*p) = reinterpret_cast<byte_t*>(p) + block_size_);
(*p) = nullptr;
}
return cursor_;
}
public:
explicit fixed_alloc(std::size_t block_size, std::size_t init_expand = 1) {
init(block_size, init_expand);
}
fixed_alloc(fixed_alloc && rhs) {
init(0, 0);
swap(rhs);
}
fixed_alloc& operator=(fixed_alloc rhs) {
swap(rhs);
return (*this);
}
void swap(fixed_alloc& rhs) {
alloc_.swap(rhs.alloc_);
base_t::swap(rhs);
}
template <typename A = AllocP>
auto take(fixed_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
base_t::take(std::move(rhs));
alloc_.take(std::move(rhs.alloc_));
}
void* alloc() {
void* p = try_expand();
cursor_ = next(p);
return p;
}
void* alloc(std::size_t) {
return alloc();
}
};
} // namespace detail
template <std::size_t BaseSize = sizeof(void*) * 1024,
std::size_t LimitSize = (std::numeric_limits<std::uint32_t>::max)()>
struct fixed_expand_policy {
enum : std::size_t {
base_size = BaseSize,
limit_size = LimitSize
};
constexpr static std::size_t prev(std::size_t e) noexcept {
return ((e / 2) == 0) ? 1 : (e / 2);
}
constexpr static std::size_t next(std::size_t e) noexcept {
return e * 2;
}
static std::size_t next(std::size_t block_size, std::size_t & e) {
auto n = ipc::detail::max<std::size_t>(block_size, base_size) * e;
e = ipc::detail::min<std::size_t>(limit_size, next(e));
return n;
}
};
template <std::size_t BlockSize,
typename AllocP = scope_alloc<>,
typename ExpandP = fixed_expand_policy<>>
class fixed_alloc : public detail::fixed_alloc<AllocP, ExpandP> {
public:
using base_t = detail::fixed_alloc<AllocP, ExpandP>;
enum : std::size_t {
block_size = ipc::detail::max<std::size_t>(BlockSize, sizeof(void*))
};
public:
explicit fixed_alloc(std::size_t init_expand)
: base_t(block_size, init_expand) {
}
fixed_alloc() : fixed_alloc(1) {}
fixed_alloc(fixed_alloc && rhs)
: base_t(std::move(rhs)) {
}
fixed_alloc& operator=(fixed_alloc rhs) {
swap(rhs);
return (*this);
}
void swap(fixed_alloc& rhs) {
base_t::swap(rhs);
}
};
////////////////////////////////////////////////////////////////
/// Variable-size blocks allocation (without alignment)
////////////////////////////////////////////////////////////////
namespace detail {
class variable_alloc_base {
protected:
byte_t * head_ = nullptr, * tail_ = nullptr;
public:
void swap(variable_alloc_base & rhs) {
std::swap(head_, rhs.head_);
std::swap(tail_, rhs.tail_);
}
std::size_t remain() const noexcept {
return static_cast<std::size_t>(tail_ - head_);
}
bool empty() const noexcept {
return remain() == 0;
}
void take(variable_alloc_base && rhs) {
if (remain() < rhs.remain()) {
// replace this by rhs
head_ = rhs.head_;
tail_ = rhs.tail_;
}
// discard rhs
rhs.head_ = rhs.tail_ = nullptr;
}
void free(void* /*p*/) {}
void free(void* /*p*/, std::size_t) {}
};
} // namespace detail
template <std::size_t ChunkSize = (sizeof(void*) * 1024), typename AllocP = scope_alloc<>>
class variable_alloc : public detail::variable_alloc_base {
public:
using base_t = detail::variable_alloc_base;
using alloc_policy = AllocP;
enum : std::size_t {
aligned_chunk_size = detail::aligned(ChunkSize, alignof(std::max_align_t))
};
private:
alloc_policy alloc_;
public:
variable_alloc() = default;
variable_alloc(variable_alloc && rhs) { swap(rhs); }
variable_alloc& operator=(variable_alloc rhs) { swap(rhs); return (*this); }
void swap(variable_alloc& rhs) {
alloc_.swap(rhs.alloc_);
base_t::swap(rhs);
}
template <typename A = AllocP>
auto take(variable_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
base_t::take(std::move(rhs));
alloc_.take(std::move(rhs.alloc_));
}
void* alloc(std::size_t size) {
/*
* byte alignment is always alignof(std::max_align_t).
*/
size = detail::aligned(size, alignof(std::max_align_t));
void* ptr;
// size would never be 0 here
if (remain() < size) {
std::size_t chunk_size = ipc::detail::max<std::size_t>(aligned_chunk_size, size);
ptr = alloc_.alloc(chunk_size);
tail_ = static_cast<byte_t*>(ptr) + chunk_size;
head_ = tail_ - (chunk_size - size);
}
else {
ptr = head_;
head_ += size;
}
return ptr;
}
};
} // namespace mem
} // namespace ipc

View File

@ -1,121 +0,0 @@
#pragma once
#include <limits> // std::numeric_limits
#include <utility> // std::forward
#include <cstddef>
#include "libipc/pool_alloc.h"
namespace ipc {
namespace mem {
////////////////////////////////////////////////////////////////
/// The allocator wrapper class for STL
////////////////////////////////////////////////////////////////
namespace detail {
template <typename T, typename AllocP>
struct rebind {
template <typename U>
using alloc_t = AllocP;
};
template <typename T, template <typename> class AllocT>
struct rebind<T, AllocT<T>> {
template <typename U>
using alloc_t = AllocT<U>;
};
} // namespace detail
template <typename T, typename AllocP>
class allocator_wrapper {
template <typename U, typename AllocU>
friend class allocator_wrapper;
public:
// type definitions
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef AllocP alloc_policy;
private:
alloc_policy alloc_;
public:
allocator_wrapper() noexcept {}
// construct by copying (do nothing)
allocator_wrapper (const allocator_wrapper<T, AllocP>&) noexcept {}
allocator_wrapper& operator=(const allocator_wrapper<T, AllocP>&) noexcept { return *this; }
// construct from a related allocator (do nothing)
template <typename U, typename AllocU> allocator_wrapper (const allocator_wrapper<U, AllocU>&) noexcept {}
template <typename U, typename AllocU> allocator_wrapper& operator=(const allocator_wrapper<U, AllocU>&) noexcept { return *this; }
allocator_wrapper (allocator_wrapper && rhs) noexcept : alloc_ ( std::move(rhs.alloc_) ) {}
allocator_wrapper& operator=(allocator_wrapper && rhs) noexcept { alloc_ = std::move(rhs.alloc_); return *this; }
public:
// the other type of std_allocator
template <typename U>
struct rebind {
using other = allocator_wrapper< U, typename detail::rebind<T, AllocP>::template alloc_t<U> >;
};
constexpr size_type max_size(void) const noexcept {
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
}
public:
pointer allocate(size_type count) noexcept {
if (count == 0) return nullptr;
if (count > this->max_size()) return nullptr;
return static_cast<pointer>(alloc_.alloc(count * sizeof(value_type)));
}
void deallocate(pointer p, size_type count) noexcept {
alloc_.free(p, count * sizeof(value_type));
}
template <typename... P>
static void construct(pointer p, P && ... params) {
ipc::mem::construct(p, std::forward<P>(params)...);
}
static void destroy(pointer p) {
ipc::mem::destruct(p);
}
};
template <class AllocP>
class allocator_wrapper<void, AllocP> {
public:
// type definitions
typedef void value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef AllocP alloc_policy;
};
template <typename T, typename U, class AllocP>
constexpr bool operator==(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
return true;
}
template <typename T, typename U, class AllocP>
constexpr bool operator!=(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
return false;
}
} // namespace mem
} // namespace ipc

View File

@ -1,110 +0,0 @@
#pragma once
#include <type_traits>
#include <limits>
#include <utility>
#include <functional>
#include <unordered_map>
#include <map>
#include <string>
#include <cstdio>
#include "libipc/def.h"
#include "libipc/memory/alloc.h"
#include "libipc/memory/wrapper.h"
#include "libipc/platform/detail.h"
namespace ipc {
namespace mem {
//using async_pool_alloc = static_wrapper<variable_wrapper<async_wrapper<
// detail::fixed_alloc<
// variable_alloc <sizeof(void*) * 1024 * 256>,
// fixed_expand_policy<sizeof(void*) * 1024, sizeof(void*) * 1024 * 256>
// >,
// default_recycler >>>;
using async_pool_alloc = ipc::mem::static_alloc;
template <typename T>
using allocator = allocator_wrapper<T, async_pool_alloc>;
} // namespace mem
namespace {
constexpr char const * pf(int) { return "%d" ; }
constexpr char const * pf(long) { return "%ld" ; }
constexpr char const * pf(long long) { return "%lld"; }
constexpr char const * pf(unsigned int) { return "%u" ; }
constexpr char const * pf(unsigned long) { return "%lu" ; }
constexpr char const * pf(unsigned long long) { return "%llu"; }
constexpr char const * pf(float) { return "%f" ; }
constexpr char const * pf(double) { return "%f" ; }
constexpr char const * pf(long double) { return "%Lf" ; }
} // internal-linkage
template <typename T>
struct hash : public std::hash<T> {};
template <typename Key, typename T>
using unordered_map = std::unordered_map<
Key, T, ipc::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<Key const, T>>
>;
template <typename Key, typename T>
using map = std::map<
Key, T, std::less<Key>, ipc::mem::allocator<std::pair<Key const, T>>
>;
template <typename Char>
using basic_string = std::basic_string<
Char, std::char_traits<Char>, ipc::mem::allocator<Char>
>;
using string = basic_string<char>;
using wstring = basic_string<wchar_t>;
template <> struct hash<string> {
std::size_t operator()(string const &val) const noexcept {
return std::hash<char const *>{}(val.c_str());
}
};
template <> struct hash<wstring> {
std::size_t operator()(wstring const &val) const noexcept {
return std::hash<wchar_t const *>{}(val.c_str());
}
};
template <typename T>
ipc::string to_string(T val) {
char buf[std::numeric_limits<T>::digits10 + 1] {};
if (std::snprintf(buf, sizeof(buf), pf(val), val) > 0) {
return buf;
}
return {};
}
/// \brief Check string validity.
constexpr bool is_valid_string(char const *str) noexcept {
return (str != nullptr) && (str[0] != '\0');
}
/// \brief Make a valid string.
inline ipc::string make_string(char const *str) {
return is_valid_string(str) ? ipc::string{str} : ipc::string{};
}
/// \brief Combine prefix from a list of strings.
inline ipc::string make_prefix(ipc::string prefix, std::initializer_list<ipc::string> args) {
prefix += "__IPC_SHM__";
for (auto const &txt: args) {
if (txt.empty()) continue;
prefix += txt;
}
return prefix;
}
} // namespace ipc

View File

@ -1,327 +0,0 @@
#pragma once
#include <tuple>
#include <thread>
#include <deque> // std::deque
#include <functional> // std::function
#include <utility> // std::forward
#include <cstddef>
#include <cassert> // assert
#include <type_traits> // std::aligned_storage_t
#include "libipc/def.h"
#include "libipc/rw_lock.h"
#include "libipc/pool_alloc.h"
#include "libipc/utility/concept.h"
#include "libipc/memory/alloc.h"
#include "libipc/platform/detail.h"
namespace ipc {
namespace mem {
////////////////////////////////////////////////////////////////
/// Thread-safe allocation wrapper
////////////////////////////////////////////////////////////////
namespace detail {
IPC_CONCEPT_(is_comparable, operator<(std::declval<Type>()));
} // namespace detail
template <typename AllocP, bool = detail::is_comparable<AllocP>::value>
class limited_recycler;
template <typename AllocP>
class limited_recycler<AllocP, true> {
public:
using alloc_policy = AllocP;
protected:
std::deque<alloc_policy> master_allocs_;
ipc::spin_lock master_lock_;
template <typename F>
void take_first_do(F && pred) {
auto it = master_allocs_.begin();
pred(const_cast<alloc_policy&>(*it));
master_allocs_.erase(it);
}
public:
void try_recover(alloc_policy & alc) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
if (master_allocs_.empty()) return;
take_first_do([&alc](alloc_policy & first) { alc.swap(first); });
}
void collect(alloc_policy && alc) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
if (master_allocs_.size() >= 32) {
take_first_do([](alloc_policy &) {}); // erase first
}
master_allocs_.emplace_back(std::move(alc));
}
IPC_CONSTEXPR_ auto try_replenish(alloc_policy&, std::size_t) noexcept {}
};
template <typename AllocP>
class default_recycler : public limited_recycler<AllocP> {
IPC_CONCEPT_(has_remain, remain());
IPC_CONCEPT_(has_empty , empty());
template <typename A>
void try_fill(A & alc) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(this->master_lock_);
if (this->master_allocs_.empty()) return;
this->take_first_do([&alc](alloc_policy & first) { alc.take(std::move(first)); });
}
public:
using alloc_policy = typename limited_recycler<AllocP>::alloc_policy;
template <typename A = AllocP>
auto try_replenish(alloc_policy & alc, std::size_t size)
-> ipc::require<detail::has_take<A>::value && has_remain<A>::value> {
if (alc.remain() >= size) return;
this->try_fill(alc);
}
template <typename A = AllocP>
auto try_replenish(alloc_policy & alc, std::size_t /*size*/)
-> ipc::require<detail::has_take<A>::value && !has_remain<A>::value && has_empty<A>::value> {
if (!alc.empty()) return;
this->try_fill(alc);
}
template <typename A = AllocP>
auto try_replenish(alloc_policy & alc, std::size_t /*size*/)
-> ipc::require<!detail::has_take<A>::value && has_empty<A>::value> {
if (!alc.empty()) return;
this->try_recover(alc);
}
template <typename A = AllocP>
IPC_CONSTEXPR_ auto try_replenish(alloc_policy & /*alc*/, std::size_t /*size*/) noexcept
-> ipc::require<(!detail::has_take<A>::value || !has_remain<A>::value) && !has_empty<A>::value> {
// Do Nothing.
}
};
template <typename AllocP>
class empty_recycler {
public:
using alloc_policy = AllocP;
IPC_CONSTEXPR_ void try_recover(alloc_policy&) noexcept {}
IPC_CONSTEXPR_ auto try_replenish(alloc_policy&, std::size_t) noexcept {}
IPC_CONSTEXPR_ void collect(alloc_policy&&) noexcept {}
};
template <typename AllocP,
template <typename> class RecyclerP = default_recycler>
class async_wrapper {
public:
using alloc_policy = AllocP;
private:
RecyclerP<alloc_policy> recycler_;
class alloc_proxy : public AllocP {
async_wrapper * w_ = nullptr;
public:
alloc_proxy(alloc_proxy && rhs) = default;
template <typename ... P>
alloc_proxy(async_wrapper* w, P && ... pars)
: AllocP(std::forward<P>(pars) ...), w_(w) {
assert(w_ != nullptr);
w_->recycler_.try_recover(*this);
}
~alloc_proxy() {
w_->recycler_.collect(std::move(*this));
}
auto alloc(std::size_t size) {
w_->recycler_.try_replenish(*this, size);
return AllocP::alloc(size);
}
};
friend class alloc_proxy;
using ref_t = alloc_proxy&;
std::function<ref_t()> get_alloc_;
public:
template <typename ... P>
async_wrapper(P ... pars) {
get_alloc_ = [this, pars ...]()->ref_t {
thread_local alloc_proxy tls(pars ...);
return tls;
};
}
void* alloc(std::size_t size) {
return get_alloc_().alloc(size);
}
void free(void* p, std::size_t size) {
get_alloc_().free(p, size);
}
};
////////////////////////////////////////////////////////////////
/// Thread-safe allocation wrapper (with spin_lock)
////////////////////////////////////////////////////////////////
template <typename AllocP, typename MutexT = ipc::spin_lock>
class sync_wrapper {
public:
using alloc_policy = AllocP;
using mutex_type = MutexT;
private:
mutex_type lock_;
alloc_policy alloc_;
public:
template <typename ... P>
sync_wrapper(P && ... pars)
: alloc_(std::forward<P>(pars) ...)
{}
void swap(sync_wrapper& rhs) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
alloc_.swap(rhs.alloc_);
}
void* alloc(std::size_t size) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
return alloc_.alloc(size);
}
void free(void* p, std::size_t size) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
alloc_.free(p, size);
}
};
////////////////////////////////////////////////////////////////
/// Variable memory allocation wrapper
////////////////////////////////////////////////////////////////
template <std::size_t BaseSize = 0, std::size_t IterSize = sizeof(void*)>
struct default_mapping_policy {
enum : std::size_t {
base_size = BaseSize,
iter_size = IterSize,
classes_size = 64
};
template <typename F, typename ... P>
IPC_CONSTEXPR_ static void foreach(F && f, P && ... params) {
for (std::size_t i = 0; i < classes_size; ++i) {
f(i, std::forward<P>(params)...);
}
}
IPC_CONSTEXPR_ static std::size_t block_size(std::size_t id) noexcept {
return (id < classes_size) ? (base_size + (id + 1) * iter_size) : 0;
}
template <typename F, typename D, typename ... P>
IPC_CONSTEXPR_ static auto classify(F && f, D && d, std::size_t size, P && ... params) {
std::size_t id = (size - base_size - 1) / iter_size;
return (id < classes_size) ?
f(id, size, std::forward<P>(params)...) :
d(size, std::forward<P>(params)...);
}
};
template <typename FixedAlloc,
typename DefaultAlloc = mem::static_alloc,
typename MappingP = default_mapping_policy<>>
class variable_wrapper {
struct initiator {
using falc_t = std::aligned_storage_t<sizeof(FixedAlloc), alignof(FixedAlloc)>;
falc_t arr_[MappingP::classes_size];
initiator() {
MappingP::foreach([](std::size_t id, falc_t * a) {
ipc::mem::construct(&initiator::at(a, id), MappingP::block_size(id));
}, arr_);
}
~initiator() {
MappingP::foreach([](std::size_t id, falc_t * a) {
ipc::mem::destruct(&initiator::at(a, id));
}, arr_);
}
static FixedAlloc & at(falc_t * arr, std::size_t id) noexcept {
return reinterpret_cast<FixedAlloc&>(arr[id]);
}
} init_;
using falc_t = typename initiator::falc_t;
public:
void swap(variable_wrapper & other) {
MappingP::foreach([](std::size_t id, falc_t * in, falc_t * ot) {
initiator::at(in, id).swap(initiator::at(ot, id));
}, init_.arr_, other.init_.arr_);
}
void* alloc(std::size_t size) {
return MappingP::classify([](std::size_t id, std::size_t size, falc_t * a) {
return initiator::at(a, id).alloc(size);
}, [](std::size_t size, falc_t *) {
return DefaultAlloc::alloc(size);
}, size, init_.arr_);
}
void free(void* p, std::size_t size) {
MappingP::classify([](std::size_t id, std::size_t size, void* p, falc_t * a) {
initiator::at(a, id).free(p, size);
}, [](std::size_t size, void* p, falc_t *) {
DefaultAlloc::free(p, size);
}, size, p, init_.arr_);
}
};
////////////////////////////////////////////////////////////////
/// Static allocation wrapper
////////////////////////////////////////////////////////////////
template <typename AllocP>
class static_wrapper {
public:
using alloc_policy = AllocP;
static alloc_policy& instance() {
static alloc_policy alloc;
return alloc;
}
static void swap(static_wrapper&) {}
static void* alloc(std::size_t size) {
return instance().alloc(size);
}
static void free(void* p, std::size_t size) {
instance().free(p, size);
}
};
} // namespace mem
} // namespace ipc

View File

@ -1,20 +1,7 @@
#ifndef LIBIPC_SRC_PLATFORM_DETAIL_H_
#define LIBIPC_SRC_PLATFORM_DETAIL_H_
// detect platform
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WINCE) || defined(_WIN32_WCE)
# define IPC_OS_WINDOWS_
#elif defined(__linux__) || defined(__linux)
# define IPC_OS_LINUX_
#elif defined(__QNX__)
# define IPC_OS_QNX_
#elif defined(__APPLE__)
#elif defined(__ANDROID__)
// TBD
#endif
#include "libipc/imp/detect_plat.h"
#if defined(__cplusplus)
@ -27,12 +14,6 @@
// pre-defined
#ifdef IPC_UNUSED_
# error "IPC_UNUSED_ has been defined."
#endif
#ifdef IPC_FALLTHROUGH_
# error "IPC_FALLTHROUGH_ has been defined."
#endif
#ifdef IPC_STBIND_
# error "IPC_STBIND_ has been defined."
#endif
@ -42,23 +23,11 @@
#if __cplusplus >= 201703L
#define IPC_UNUSED_ [[maybe_unused]]
#define IPC_FALLTHROUGH_ [[fallthrough]]
#define IPC_STBIND_(A, B, ...) auto [A, B] = __VA_ARGS__
#define IPC_CONSTEXPR_ constexpr
#else /*__cplusplus < 201703L*/
#if defined(_MSC_VER)
# define IPC_UNUSED_ __pragma(warning(suppress: 4100 4101 4189))
#elif defined(__GNUC__)
# define IPC_UNUSED_ __attribute__((__unused__))
#else
# define IPC_UNUSED_
#endif
#define IPC_FALLTHROUGH_
#define IPC_STBIND_(A, B, ...) \
auto tp___ = __VA_ARGS__ \
auto A = std::get<0>(tp___); \

View File

@ -0,0 +1,43 @@
/**
* \file libipc/platform/gnuc/demangle.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include <cxxabi.h> // abi::__cxa_demangle
#include <cstdlib> // std::malloc
#include "libipc/imp/nameof.h"
#include "libipc/imp/scope_exit.h"
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \brief The conventional way to obtain demangled symbol name.
* \see https://www.boost.org/doc/libs/1_80_0/libs/core/doc/html/core/demangle.html
*
* \param name the mangled name
* \return std::string a human-readable demangled type name
*/
std::string demangle(std::string name) noexcept {
/// \see https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html
std::size_t sz = name.size() + 1;
char *buffer = static_cast<char *>(std::malloc(sz));
int status = 0;
char *realname = abi::__cxa_demangle(name.data(), buffer, &sz, &status);
if (realname == nullptr) {
std::free(buffer);
return {};
}
LIBIPC_SCOPE_EXIT(guard) = [realname] {
std::free(realname);
};
LIBIPC_TRY {
return std::move(name.assign(realname, sz));
} LIBIPC_CATCH(...) {
return {};
}
}
} // namespace ipc

View File

@ -7,7 +7,7 @@
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
@ -108,7 +108,7 @@ class mutex {
shm_data(init arg)
: mtx{}, ref{0} { mtx.open(arg.name); }
};
ipc::map<ipc::string, shm_data> mutex_handles;
ipc::map<std::string, shm_data> mutex_handles;
std::mutex lock;
static curr_prog &get() {
@ -122,7 +122,7 @@ class mutex {
return;
}
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
it = info.mutex_handles
@ -136,10 +136,10 @@ class mutex {
}
template <typename F>
static void release_mutex(ipc::string const &name, F &&clear) {
static void release_mutex(std::string const &name, F &&clear) {
if (name.empty()) return;
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
return;

View File

@ -1,13 +1,13 @@
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#elif defined(IPC_OS_LINUX_)
#if defined(LIBIPC_OS_WIN)
#elif defined(LIBIPC_OS_LINUX)
#include "libipc/platform/linux/a0/err.c"
#include "libipc/platform/linux/a0/mtx.c"
#include "libipc/platform/linux/a0/strconv.c"
#include "libipc/platform/linux/a0/tid.c"
#include "libipc/platform/linux/a0/time.c"
#elif defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_QNX)
#else/*IPC_OS*/
# error "Unsupported platform."
#endif

View File

@ -1,8 +1,8 @@
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/shm_win.cpp"
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_LINUX) || defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/shm_posix.cpp"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -63,7 +63,7 @@ public:
ipc::error("fail pthread_condattr_init[%d]\n", eno);
return false;
}
IPC_UNUSED_ auto guard_cond_attr = guard([&cond_attr] { ::pthread_condattr_destroy(&cond_attr); });
LIBIPC_UNUSED auto guard_cond_attr = guard([&cond_attr] { ::pthread_condattr_destroy(&cond_attr); });
if ((eno = ::pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED)) != 0) {
ipc::error("fail pthread_condattr_setpshared[%d]\n", eno);
return false;

View File

@ -12,7 +12,7 @@
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/utility/scope_guard.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
@ -38,7 +38,7 @@ class mutex {
shm_data(init arg)
: shm{arg.name, arg.size}, ref{0} {}
};
ipc::map<ipc::string, shm_data> mutex_handles;
ipc::map<std::string, shm_data> mutex_handles;
std::mutex lock;
static curr_prog &get() {
@ -52,7 +52,7 @@ class mutex {
return nullptr;
}
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
it = info.mutex_handles
@ -71,10 +71,10 @@ class mutex {
}
template <typename F>
static void release_mutex(ipc::string const &name, F &&clear) {
static void release_mutex(std::string const &name, F &&clear) {
if (name.empty()) return;
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
return;
@ -130,7 +130,7 @@ public:
ipc::error("fail pthread_mutexattr_init[%d]\n", eno);
return false;
}
IPC_UNUSED_ auto guard_mutex_attr = guard([&mutex_attr] { ::pthread_mutexattr_destroy(&mutex_attr); });
LIBIPC_UNUSED auto guard_mutex_attr = guard([&mutex_attr] { ::pthread_mutexattr_destroy(&mutex_attr); });
if ((eno = ::pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED)) != 0) {
ipc::error("fail pthread_mutexattr_setpshared[%d]\n", eno);
return false;

View File

@ -13,10 +13,10 @@
#include "libipc/shm.h"
#include "libipc/def.h"
#include "libipc/pool_alloc.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/mem/new.h"
namespace {
@ -28,7 +28,7 @@ struct id_info_t {
int fd_ = -1;
void* mem_ = nullptr;
std::size_t size_ = 0;
ipc::string name_;
std::string name_;
};
constexpr std::size_t calc_size(std::size_t size) {
@ -51,7 +51,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
}
// For portable use, a shared memory object should be identified by name of the form /somename.
// see: https://man7.org/linux/man-pages/man3/shm_open.3.html
ipc::string op_name = ipc::string{"/"} + name;
std::string op_name = std::string{"/"} + name;
// Open the object for read-write access.
int flag = O_RDWR;
switch (mode) {
@ -81,7 +81,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
::fchmod(fd, S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH);
auto ii = mem::alloc<id_info_t>();
auto ii = mem::$new<id_info_t>();
ii->fd_ = fd;
ii->size_ = size;
ii->name_ = std::move(op_name);
@ -177,7 +177,7 @@ std::int32_t release(id_t id) noexcept {
}
}
else ::munmap(ii->mem_, ii->size_);
mem::free(ii);
mem::$delete(ii);
return ret;
}

View File

@ -0,0 +1,49 @@
/**
* \file libipc/platform/posix/system.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "libipc/imp/system.h"
#include "libipc/imp/log.h"
namespace ipc {
namespace sys {
/**
* \brief Get the system error number.
* \see https://en.cppreference.com/w/cpp/error/generic_category
* https://man7.org/linux/man-pages/man3/errno.3.html
*/
std::error_code error() noexcept {
return std::error_code(errno, std::generic_category());
}
/**
* \brief Gets configuration information at run time
* https://man7.org/linux/man-pages/man2/getpagesize.2.html
* https://man7.org/linux/man-pages/man3/sysconf.3.html
*/
result<std::int64_t> conf(info r) noexcept {
LIBIPC_LOG();
switch (r) {
case info::page_size: {
auto val = ::sysconf(_SC_PAGESIZE);
if (val >= 0) return static_cast<std::int64_t>(val);
break;
}
default:
log.error("invalid info = ", underlyof(r));
return std::make_error_code(std::errc::invalid_argument);
}
auto err = sys::error();
log.error("info = ", underlyof(r), ", error = ", err);
return err;
}
} // namespace sys
} // namespace ipc

View File

@ -0,0 +1,95 @@
/**
* \file libipc/platform/win/codecvt.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include <Windows.h>
#include "libipc/imp/codecvt.h"
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \see https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
*
* CP_ACP : The system default Windows ANSI code page.
* CP_MACCP : The current system Macintosh code page.
* CP_OEMCP : The current system OEM code page.
* CP_SYMBOL : Symbol code page (42).
* CP_THREAD_ACP: The Windows ANSI code page for the current thread.
* CP_UTF7 : UTF-7. Use this value only when forced by a 7-bit transport mechanism. Use of UTF-8 is preferred.
* CP_UTF8 : UTF-8.
*/
template <>
std::size_t cvt_cstr(char const *src, std::size_t slen, wchar_t *des, std::size_t dlen) noexcept {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
int cch_wc = (des == nullptr) ? 0 : (int)dlen;
int size_needed = ::MultiByteToWideChar(CP_ACP, 0, src, (int)slen, des, cch_wc);
if (size_needed <= 0) {
// failed: MultiByteToWideChar(CP_ACP).
return 0;
}
return size_needed;
}
template <>
std::size_t cvt_cstr(wchar_t const *src, std::size_t slen, char *des, std::size_t dlen) noexcept {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
int cb_mb = (des == nullptr) ? 0 : (int)dlen;
int size_needed = ::WideCharToMultiByte(CP_ACP, 0, src, (int)slen, des, cb_mb, NULL, NULL);
if (size_needed <= 0) {
// failed: WideCharToMultiByte(CP_ACP).
return 0;
}
return size_needed;
}
/**
* \brief Used for char8_t (since C++20) to wchar_t conversion.
*
* There is no ut to guarantee correctness (I'm a little lazy here),
* so if there are any bugs, please contact me in time.
*/
#if defined(LIBIMP_CPP_20)
template <>
std::size_t cvt_cstr(char8_t const *src, std::size_t slen, wchar_t *des, std::size_t dlen) noexcept {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
int cch_wc = (des == nullptr) ? 0 : (int)dlen;
int size_needed = ::MultiByteToWideChar(CP_UTF8, 0, (char *)src, (int)slen, des, cch_wc);
if (size_needed <= 0) {
// failed: MultiByteToWideChar(CP_UTF8).
return 0;
}
return size_needed;
}
template <>
std::size_t cvt_cstr(wchar_t const *src, std::size_t slen, char8_t *des, std::size_t dlen) noexcept {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
int cb_mb = (des == nullptr) ? 0 : (int)dlen;
int size_needed = ::WideCharToMultiByte(CP_UTF8, 0, src, (int)slen, (char *)des, cb_mb, NULL, NULL);
if (size_needed <= 0) {
// failed: WideCharToMultiByte(CP_UTF8).
return 0;
}
return size_needed;
}
#endif // defined(LIBIMP_CPP_20)
} // namespace ipc

View File

@ -81,7 +81,7 @@ public:
if (!valid()) return false;
auto &cnt = counter();
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> guard {lock_};
cnt = (cnt < 0) ? 1 : cnt + 1;
}
DWORD ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
@ -93,7 +93,7 @@ public:
bool rs = ::SignalObjectAndWait(mtx.native(), sem_.native(), ms, FALSE) == WAIT_OBJECT_0;
bool rl = mtx.lock(); // INFINITE
if (!rs) {
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> guard {lock_};
cnt -= 1;
}
return rs && rl;

View File

@ -0,0 +1,15 @@
/**
* \file libipc/platform/win/demangle.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include "libipc/imp/nameof.h"
namespace ipc {
std::string demangle(std::string name) noexcept {
return std::move(name);
}
} // namespace ipc

View File

@ -84,7 +84,7 @@ public:
return false;
case WAIT_ABANDONED:
unlock();
IPC_FALLTHROUGH_;
LIBIPC_FALLTHROUGH;
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
throw std::system_error{static_cast<int>(ret), std::system_category()};

View File

@ -6,10 +6,10 @@
#include "libipc/shm.h"
#include "libipc/def.h"
#include "libipc/pool_alloc.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/mem/new.h"
#include "to_tchar.h"
#include "get_sa.h"
@ -58,7 +58,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
return nullptr;
}
}
auto ii = mem::alloc<id_info_t>();
auto ii = mem::$new<id_info_t>();
ii->h_ = h;
ii->size_ = size;
return ii;
@ -116,7 +116,7 @@ std::int32_t release(id_t id) noexcept {
ipc::error("fail release: invalid id (h = null)\n");
}
else ::CloseHandle(ii->h_);
mem::free(ii);
mem::$delete(ii);
return 0;
}

View File

@ -0,0 +1,88 @@
/**
* \file libipc/platform/win/system.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include <exception>
#include <type_traits>
#include <Windows.h>
#include <tchar.h>
#include "libipc/imp/system.h"
#include "libipc/imp/log.h"
#include "libipc/imp/codecvt.h"
#include "libipc/imp/generic.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/scope_exit.h"
namespace ipc {
namespace sys {
/**
* \brief Gets a text description of the system error
* \see https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessage
*/
std::string error_string(DWORD code) noexcept {
LIBIPC_LOG();
LIBIPC_TRY {
LPTSTR lpErrText = NULL;
if (::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpErrText,
0, NULL) == 0) {
log.error("failed: FormatMessage(dwMessageId = ", code, "). error = ", ::GetLastError());
return {};
}
LIBIPC_SCOPE_EXIT(finally) = [lpErrText] { ::LocalFree(lpErrText); };
std::size_t msg_len = ::_tcslen(lpErrText);
std::size_t len = cvt_cstr(lpErrText, msg_len, (char *)nullptr, 0);
if (len == 0) {
return {};
}
std::string ret(len, '\0');
cvt_cstr(lpErrText, msg_len, &ret[0], ret.size());
return ret;
} LIBIPC_CATCH(...) {
log.error("failed: FormatMessage(dwMessageId = ", code, ").",
"\n\texception: ", log::exception_string(std::current_exception()));
}
return {};
}
/**
* \brief Get the system error number.
* \see https://en.cppreference.com/w/cpp/error/system_category
* https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
*/
std::error_code error() noexcept {
return std::error_code(::GetLastError(), std::system_category());
}
/**
* \brief Retrieves information about the current system.
* \see https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo
* https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo
*/
result<std::int64_t> conf(info r) noexcept {
LIBIPC_LOG();
switch (r) {
case info::page_size: {
::SYSTEM_INFO info{};
::GetNativeSystemInfo(&info);
return (std::int64_t)info.dwPageSize;
}
default:
log.error("invalid info = ", underlyof(r));
return std::make_error_code(std::errc::invalid_argument);
}
}
} // namespace sys
} // namespace ipc

View File

@ -11,7 +11,7 @@
#include <cstddef>
#include "libipc/utility/concept.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/platform/detail.h"
namespace ipc {
@ -36,7 +36,7 @@ using IsSameChar = ipc::require<is_same_char<T, S>::value, R>;
////////////////////////////////////////////////////////////////
template <typename T = TCHAR>
constexpr auto to_tchar(ipc::string &&str) -> IsSameChar<T, ipc::string, ipc::string &&> {
constexpr auto to_tchar(std::string &&str) -> IsSameChar<T, std::string, std::string &&> {
return std::move(str); // noconv
}
@ -48,7 +48,7 @@ constexpr auto to_tchar(ipc::string &&str) -> IsSameChar<T, ipc::string, ipc::st
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
*/
template <typename T = TCHAR>
auto to_tchar(ipc::string &&external) -> IsSameChar<T, ipc::wstring> {
auto to_tchar(std::string &&external) -> IsSameChar<T, std::wstring> {
if (external.empty()) {
return {}; // noconv
}
@ -65,7 +65,7 @@ auto to_tchar(ipc::string &&external) -> IsSameChar<T, ipc::wstring> {
if (size_needed <= 0) {
return {};
}
ipc::wstring internal(size_needed, L'\0');
std::wstring internal(size_needed, L'\0');
::MultiByteToWideChar(CP_UTF8, 0, &external[0], (int)external.size(), &internal[0], size_needed);
return internal;
}

View File

@ -1,17 +0,0 @@
#include "libipc/pool_alloc.h"
#include "libipc/memory/resource.h"
namespace ipc {
namespace mem {
void* pool_alloc::alloc(std::size_t size) noexcept {
return async_pool_alloc::alloc(size);
}
void pool_alloc::free(void* p, std::size_t size) noexcept {
async_pool_alloc::free(p, size);
}
} // namespace mem
} // namespace ipc

View File

@ -18,7 +18,7 @@
#include "libipc/utility/log.h"
#include "libipc/platform/detail.h"
#include "libipc/circ/elem_def.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
namespace ipc {
namespace detail {

View File

@ -6,7 +6,7 @@
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
namespace ipc {
namespace shm {
@ -16,7 +16,7 @@ public:
shm::id_t id_ = nullptr;
void* m_ = nullptr;
ipc::string n_;
std::string n_;
std::size_t s_ = 0;
};

View File

@ -3,13 +3,13 @@
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/condition.h"
#elif defined(IPC_OS_LINUX_)
#elif defined(LIBIPC_OS_LINUX)
#include "libipc/platform/linux/condition.h"
#elif defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/condition.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -3,13 +3,13 @@
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/mutex.h"
#elif defined(IPC_OS_LINUX_)
#elif defined(LIBIPC_OS_LINUX)
#include "libipc/platform/linux/mutex.h"
#elif defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/mutex.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -3,11 +3,11 @@
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/semaphore.h"
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_LINUX) || defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/semaphore_impl.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -1,11 +1,11 @@
#include "libipc/waiter.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/mutex.h"
#elif defined(IPC_OS_LINUX_)
#elif defined(LIBIPC_OS_LINUX)
#include "libipc/platform/linux/mutex.h"
#elif defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/mutex.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -5,7 +5,7 @@
#include "libipc/platform/detail.h"
#include "libipc/utility/concept.h"
#include "libipc/pool_alloc.h"
#include "libipc/mem/new.h"
namespace ipc {
@ -36,12 +36,12 @@ IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplComfortable<T, void> {
template <typename T, typename... P>
IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplUncomfortable<T> {
return mem::alloc<T>(std::forward<P>(params)...);
return mem::$new<T>(std::forward<P>(params)...);
}
template <typename T>
IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplUncomfortable<T, void> {
mem::free(p);
mem::$delete(p);
}
template <typename T>

View File

@ -27,7 +27,7 @@ constexpr decltype(auto) static_switch(std::size_t i, F&& f, D&& def) {
template <typename F, std::size_t...I>
IPC_CONSTEXPR_ void static_for(std::index_sequence<I...>, F&& f) {
IPC_UNUSED_ auto expand = { (std::forward<F>(f)(std::integral_constant<std::size_t, I>{}), 0)... };
LIBIPC_UNUSED auto expand = { (std::forward<F>(f)(std::integral_constant<std::size_t, I>{}), 0)... };
}
template <std::size_t N, typename F>
@ -44,18 +44,6 @@ enum {
// #endif/*__cplusplus < 201703L*/
};
template <typename T, typename U>
auto horrible_cast(U rhs) noexcept
-> typename std::enable_if<std::is_trivially_copyable<T>::value
&& std::is_trivially_copyable<U>::value, T>::type {
union {
T t;
U u;
} r = {};
r.u = rhs;
return r.t;
}
IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) {
// align must be 2^n
return (size + align - 1) & ~(align - 1);

View File

@ -63,7 +63,7 @@ public:
template <typename F>
bool wait_if(F &&pred, std::uint64_t tm = ipc::invalid_value) noexcept {
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> guard {lock_};
while ([this, &pred] {
return !quit_.load(std::memory_order_relaxed)
&& std::forward<F>(pred)();
@ -75,14 +75,14 @@ public:
bool notify() noexcept {
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
}
return cond_.notify(lock_);
}
bool broadcast() noexcept {
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
}
return cond_.broadcast(lock_);
}

View File

@ -17,6 +17,9 @@ include_directories(
file(GLOB SRC_FILES
${LIBIPC_PROJECT_DIR}/test/*.cpp
${LIBIPC_PROJECT_DIR}/test/imp/*.cpp
${LIBIPC_PROJECT_DIR}/test/mem/*.cpp
${LIBIPC_PROJECT_DIR}/test/concur/*.cpp
# ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp
)
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/*.h)

View File

@ -0,0 +1,103 @@
#include "test.h"
#define private public
#include "libipc/concur/intrusive_stack.h"
using namespace ipc;
TEST(intrusive_stack, construct) {
concur::intrusive_stack<int> s;
EXPECT_TRUE(s.empty());
}
TEST(intrusive_stack, construct_node) {
concur::intrusive_stack<int>::node n{};
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
}
TEST(intrusive_stack, copyable) {
EXPECT_FALSE(std::is_copy_constructible<concur::intrusive_stack<int>>::value);
EXPECT_FALSE(std::is_copy_assignable<concur::intrusive_stack<int>>::value);
}
TEST(intrusive_stack, moveable) {
EXPECT_FALSE(std::is_move_constructible<concur::intrusive_stack<int>>::value);
EXPECT_FALSE(std::is_move_assignable<concur::intrusive_stack<int>>::value);
}
TEST(intrusive_stack, push_one) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n{123};
s.push(&n);
EXPECT_FALSE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n);
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
EXPECT_EQ(n.value, 123);
}
TEST(intrusive_stack, push_many) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n1{111111};
concur::intrusive_stack<int>::node n2{222222};
concur::intrusive_stack<int>::node n3{333333};
s.push(&n1);
s.push(&n2);
s.push(&n3);
EXPECT_FALSE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n3);
EXPECT_TRUE(n3.next.load(std::memory_order_relaxed) == &n2);
EXPECT_TRUE(n2.next.load(std::memory_order_relaxed) == &n1);
EXPECT_TRUE(n1.next.load(std::memory_order_relaxed) == nullptr);
EXPECT_EQ(n1.value, 111111);
EXPECT_EQ(n2.value, 222222);
EXPECT_EQ(n3.value, 333333);
}
TEST(intrusive_stack, push_same) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n{321};
s.push(&n);
s.push(&n);
EXPECT_FALSE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n);
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == &n);
EXPECT_EQ(n.value, 321);
}
TEST(intrusive_stack, pop_empty) {
concur::intrusive_stack<int> s;
EXPECT_TRUE(s.pop() == nullptr);
}
TEST(intrusive_stack, pop_one) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n{112233};
s.push(&n);
EXPECT_TRUE(s.pop() == &n);
EXPECT_TRUE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == nullptr);
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
EXPECT_EQ(n.value, 112233);
}
TEST(intrusive_stack, pop_many) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n1{111111};
concur::intrusive_stack<int>::node n2{222222};
concur::intrusive_stack<int>::node n3{333333};
s.push(&n1);
s.push(&n2);
s.push(&n3);
EXPECT_TRUE(s.pop() == &n3);
EXPECT_TRUE(s.pop() == &n2);
EXPECT_TRUE(s.pop() == &n1);
EXPECT_TRUE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == nullptr);
EXPECT_TRUE(n3.next.load(std::memory_order_relaxed) == &n2);
EXPECT_TRUE(n2.next.load(std::memory_order_relaxed) == &n1);
EXPECT_TRUE(n1.next.load(std::memory_order_relaxed) == nullptr);
EXPECT_EQ(n1.value, 111111);
EXPECT_EQ(n2.value, 222222);
EXPECT_EQ(n3.value, 333333);
}

View File

@ -0,0 +1,57 @@
#include "test.h"
#include "libipc/imp/byte.h"
#include "libipc/imp/span.h"
#include "libipc/imp/detect_plat.h"
TEST(byte, construct) {
{
LIBIPC_UNUSED ipc::byte b;
SUCCEED();
}
{
ipc::byte b{};
EXPECT_EQ(int(b), 0);
}
{
ipc::byte b{123};
EXPECT_EQ(int(b), 123);
}
{
ipc::byte b{65535};
EXPECT_EQ(int(b), 255);
EXPECT_EQ(std::int8_t(b), -1);
}
{
ipc::byte b{65536};
EXPECT_EQ(int(b), 0);
}
}
TEST(byte, compare) {
{
ipc::byte b1{}, b2{};
EXPECT_EQ(b1, b2);
}
{
ipc::byte b1{}, b2(321);
EXPECT_NE(b1, b2);
}
}
TEST(byte, byte_cast) {
int a = 654321;
int *pa = &a;
// int * => byte *
ipc::byte *pb = ipc::byte_cast(pa);
EXPECT_EQ((std::size_t)pb, (std::size_t)pa);
// byte * => int32_t *
std::int32_t *pc = ipc::byte_cast<std::int32_t>(pb);
EXPECT_EQ(*pc, a);
// byte alignment check
EXPECT_EQ(ipc::byte_cast<int>(pb + 1), nullptr);
}

View File

@ -0,0 +1,40 @@
#include <string>
#include <cstring>
#include "test.h"
#include "libipc/imp/codecvt.h"
TEST(codecvt, cvt_cstr) {
char const utf8[] = "hello world, 你好,こんにちは";
wchar_t const utf16[] = L"hello world, 你好,こんにちは";
{
auto cvt_len = ipc::cvt_cstr(utf8, std::strlen(utf8), (wchar_t *)nullptr, 0);
EXPECT_NE(cvt_len, 0);
std::wstring wstr(cvt_len, L'\0');
EXPECT_EQ(ipc::cvt_cstr(utf8, std::strlen(utf8), &wstr[0], wstr.size()), cvt_len);
EXPECT_EQ(wstr, utf16);
}
{
auto cvt_len = ipc::cvt_cstr(utf16, std::wcslen(utf16), (char *)nullptr, 0);
EXPECT_NE(cvt_len, 0);
std::string str(cvt_len, '\0');
EXPECT_EQ(ipc::cvt_cstr(utf16, std::wcslen(utf16), &str[0], str.size()), cvt_len);
EXPECT_EQ(str, utf8);
}
{
auto cvt_len = ipc::cvt_cstr(utf8, std::strlen(utf8), (char *)nullptr, 0);
EXPECT_EQ(cvt_len, std::strlen(utf8));
std::string str(cvt_len, '\0');
EXPECT_EQ(ipc::cvt_cstr(utf8, cvt_len, &str[0], str.size()), cvt_len);
EXPECT_EQ(str, utf8);
}
{
auto cvt_len = ipc::cvt_cstr(utf16, std::wcslen(utf16), (wchar_t *)nullptr, 0);
EXPECT_EQ(cvt_len, std::wcslen(utf16));
std::wstring wstr(cvt_len, u'\0');
EXPECT_EQ(ipc::cvt_cstr(utf16, cvt_len, &wstr[0], wstr.size()), cvt_len);
EXPECT_EQ(wstr, utf16);
}
}

View File

@ -0,0 +1,90 @@
#include "test.h"
#include "libipc/imp/detect_plat.h"
TEST(detect_plat, os) {
#if defined(LIBIPC_OS_WINCE)
std::cout << "LIBIPC_OS_WINCE\n";
#elif defined(LIBIPC_OS_WIN)
std::cout << "LIBIPC_OS_WIN\n";
#elif defined(LIBIPC_OS_LINUX)
std::cout << "LIBIPC_OS_LINUX\n";
#elif defined(LIBIPC_OS_QNX)
std::cout << "LIBIPC_OS_QNX\n";
#elif defined(LIBIPC_OS_ANDROID)
std::cout << "LIBIPC_OS_ANDROID\n";
#else
ASSERT_TRUE(false);
#endif
SUCCEED();
}
TEST(detect_plat, cc) {
#if defined(LIBIPC_CC_MSVC)
std::cout << "LIBIPC_CC_MSVC\n";
#elif defined(LIBIPC_CC_GNUC)
std::cout << "LIBIPC_CC_GNUC\n";
#else
ASSERT_TRUE(false);
#endif
SUCCEED();
}
TEST(detect_plat, cpp) {
#if defined(LIBIPC_CPP_20)
std::cout << "LIBIPC_CPP_20\n";
#elif defined(LIBIPC_CPP_17)
std::cout << "LIBIPC_CPP_17\n";
#elif defined(LIBIPC_CPP_14)
std::cout << "LIBIPC_CPP_14\n";
#else
ASSERT_TRUE(false);
#endif
SUCCEED();
}
TEST(detect_plat, byte_order) {
auto is_endian_little = [] {
union {
std::int32_t a;
std::int8_t b;
} c;
c.a = 1;
return c.b == 1;
};
EXPECT_EQ(!!LIBIPC_ENDIAN_LIT, is_endian_little());
EXPECT_NE(!!LIBIPC_ENDIAN_BIG, is_endian_little());
}
TEST(detect_plat, fallthrough) {
switch (0) {
case 0:
std::cout << "fallthrough 0\n";
LIBIPC_FALLTHROUGH;
case 1:
std::cout << "fallthrough 1\n";
LIBIPC_FALLTHROUGH;
default:
std::cout << "fallthrough default\n";
break;
}
SUCCEED();
}
TEST(detect_plat, unused) {
LIBIPC_UNUSED int abc;
SUCCEED();
}
TEST(detect_plat, likely_unlikely) {
int xx = sizeof(int);
if LIBIPC_LIKELY(xx < sizeof(long long)) {
std::cout << "sizeof(int) < sizeof(long long)\n";
} else if LIBIPC_UNLIKELY(xx < sizeof(char)) {
std::cout << "sizeof(int) < sizeof(char)\n";
} else {
std::cout << "sizeof(int) < whatever\n";
}
SUCCEED();
}

View File

@ -0,0 +1,12 @@
#include <iostream>
#include "test.h"
#include "libipc/imp/error.h"
#include "libipc/imp/fmt.h"
TEST(error, error_code) {
std::error_code ecode;
EXPECT_FALSE(ecode);
std::cout << ipc::fmt(ecode, '\n');
}

View File

@ -0,0 +1,142 @@
#include "test.h"
#include "libipc/imp/expected.h"
namespace {
class test_val {
public:
int val = 0;
int dc_ = 0;
int cc_ = 0;
int mv_ = 0;
test_val() {
dc_ += 1;
std::printf("test_val construct.\n");
}
test_val(test_val const &o)
: val(o.val) {
cc_ = o.cc_ + 1;
std::printf("test_val copy construct.\n");
}
test_val(test_val &&o) noexcept
: val(std::exchange(o.val, 0)) {
mv_ = o.mv_ + 1;
std::printf("test_val move construct.\n");
}
~test_val() {
std::printf("test_val destruct.\n");
}
test_val &operator=(test_val &&o) noexcept {
mv_ = o.mv_ + 1;
val = std::exchange(o.val, 0);
return *this;
}
test_val(int v)
: val(v) {
std::printf("test_val value initialization.\n");
}
bool operator==(test_val const &rhs) const {
return val == rhs.val;
}
};
class test_err {
public:
int dc_ = 0;
int cc_ = 0;
int mv_ = 0;
std::int64_t val = 0;
test_err() {
dc_ += 1;
std::printf("test_err construct.\n");
}
test_err(test_err const &o)
: val(o.val) {
cc_ = o.cc_ + 1;
std::printf("test_err copy construct.\n");
}
test_err(test_err &&o) noexcept
: val(std::exchange(o.val, 0)) {
mv_ = o.mv_ + 1;
std::printf("test_err move construct.\n");
}
~test_err() {
std::printf("test_err destruct.\n");
}
test_err &operator=(test_err &&o) noexcept {
mv_ = o.mv_ + 1;
val = std::exchange(o.val, 0);
return *this;
}
test_err(int v)
: val(v) {
std::printf("test_err value initialization.\n");
}
bool operator==(test_err const &rhs) const {
return val == rhs.val;
}
};
} // namespace
TEST(expected, in_place) {
ipc::expected<test_val, test_err> e1;
EXPECT_TRUE(e1);
EXPECT_EQ(e1.value().dc_, 1);
EXPECT_EQ(e1.value().val, 0);
ipc::expected<test_val, test_err> e2 {ipc::in_place, 123};
EXPECT_TRUE(e2);
EXPECT_EQ(e2.value().dc_, 0);
EXPECT_EQ(e2.value().val, 123);
}
TEST(expected, unexpected) {
ipc::expected<test_val, test_err> e1 {ipc::unexpected};
EXPECT_FALSE(e1);
EXPECT_EQ(e1.error().dc_, 1);
EXPECT_EQ(e1.error().val, 0);
ipc::expected<test_val, test_err> e2 {ipc::unexpected, 321};
EXPECT_FALSE(e2);
EXPECT_EQ(e2.error().dc_, 0);
EXPECT_EQ(e2.error().val, 321);
}
TEST(expected, copy_and_move) {
ipc::expected<test_val, test_err> e1 {ipc::in_place, 123};
ipc::expected<test_val, test_err> e2 {e1};
EXPECT_TRUE(e1);
EXPECT_TRUE(e2);
EXPECT_EQ(e1, e2);
EXPECT_EQ(e2.value().cc_, 1);
EXPECT_EQ(e2.value().val, 123);
ipc::expected<test_val, test_err> e3 {ipc::unexpected, 333};
ipc::expected<test_val, test_err> e4 {e3};
EXPECT_FALSE(e3);
EXPECT_FALSE(e4);
EXPECT_EQ(e3, e4);
EXPECT_EQ(e4.error().cc_, 1);
EXPECT_EQ(e4.error().val, 333);
ipc::expected<test_val, test_err> e5;
e5 = e1;
EXPECT_EQ(e1, e5);
EXPECT_EQ(e5.value().val, 123);
ipc::expected<test_val, test_err> e6;
e6 = std::move(e5);
EXPECT_EQ(e1, e6);
EXPECT_EQ(e5.value().val, 0);
}

193
test/imp/test_imp_fmt.cpp Normal file
View File

@ -0,0 +1,193 @@
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <tuple>
#include <cstring>
#include "test.h"
#include "libipc/imp/fmt.h"
#include "libipc/imp/byte.h"
#include "libipc/imp/span.h"
#include "libipc/imp/result.h"
TEST(fmt, spec) {
EXPECT_STREQ(ipc::spec("hello")(123).fstr.data(), "hello");
EXPECT_EQ(ipc::spec("hello")(123).param , 123);
EXPECT_STREQ(ipc::spec("hello")("world").fstr.data(), "hello");
EXPECT_STREQ(ipc::spec("hello")("world").param , "world");
}
TEST(fmt, to_string) {
std::string joined;
ipc::fmt_context ctx(joined);
auto check = [&](auto &&txt, auto &&...val) {
ctx.reset();
EXPECT_TRUE(ipc::to_string(ctx, std::forward<decltype(val)>(val)...));
ctx.finish();
EXPECT_EQ(joined, std::forward<decltype(txt)>(txt));
};
/// \brief string
check("", "");
check("%what%", "%what%");
check(" %what%", "%what%", "10");
check("%what% ", "%what%", "-10");
/// \brief character
check("A", 'A');
check("A", L'A');
check("A", u'A');
check("A", U'A');
/// \brief numeric
check("123" , (signed char)123 );
check("-65" , (signed char)-321 );
check("123" , (unsigned char)123 );
check("65" , (unsigned char)321 );
check("123" , (short)123 );
check("-321" , (short)-321 );
check("123" , (unsigned short)123 );
check("321" , (unsigned short)321 );
check("-7949" , (short)123123 );
check("6359" , (short)-321321 );
check("57587" , (unsigned short)123123);
check("59177" , (unsigned short)321321);
check("123123" , 123123 );
check("-321321" , -321321 );
check("123123" , 123123u );
check("321321" , 321321u );
check("123123" , 123123ll );
check("-321321" , -321321ll );
check("123123" , 123123ull );
check("321321" , 321321ull );
check("1e0f3" , 123123, "x" );
check("1e0f3" , 123123u, "x" );
check("1CAAB5C3B3", 123123123123ll, "X" );
check("1CAAB5C3B3", 123123123123ull, "X" );
/// \brief floating point
check("123.123" , 123.123f, ".3");
check("0123.12300" , 123.123, "010.5");
check("123.123000" , 123.123l, "010.6");
check("1.500000e+00", 1.5, "e");
check("1.500000E+00", 1.5, "E");
double r = 0.0;
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, 0.0/r)); ctx.finish();
std::cout << joined << "\n";
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, 1.0/r)); ctx.finish();
std::cout << joined << "\n";
/// \brief pointer
check("null", nullptr);
int *p = (int *)0x0f013a04;
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, (void *)p)); ctx.finish();
std::cout << joined << "\n";
/// \brief date and time
auto tp = std::chrono::system_clock::now();
auto tt = std::chrono::system_clock::to_time_t(tp);
auto tm = *std::localtime(&tt);
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, tm)); ctx.finish();
std::cout << joined << "\n";
std::string tm_str = joined;
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, tp)); ctx.finish();
EXPECT_EQ(tm_str, joined);
}
TEST(fmt, fmt) {
char const txt[] = "hello world.";
/// \brief hello world
auto s = ipc::fmt("hello", " ", "world", ".");
EXPECT_EQ(s, txt);
/// \brief chrono
std::cout << ipc::fmt('[', std::chrono::system_clock::now(), "] ", s) << "\n";
/// \brief long string
s = ipc::fmt(ipc::spec("4096")(txt));
std::string test(4096, ' ');
std::memcpy(&test[test.size() - sizeof(txt) + 1], txt, sizeof(txt) - 1);
EXPECT_EQ(s, test);
EXPECT_EQ(ipc::fmt("", 1, "", '2', "", 3.0), "123.000000");
char const * nc = nullptr;
EXPECT_EQ(ipc::fmt(nc, 1, "", '2', "", 3.0), "123.000000");
std::string empty;
EXPECT_EQ(ipc::fmt(empty, 1, "", '2', "", 3.0), "123.000000");
}
namespace {
class foo {};
bool tag_invoke(decltype(ipc::fmt_to), ipc::fmt_context &, foo arg) noexcept(false) {
throw arg;
return {};
}
} // namespace
TEST(fmt, throw) {
EXPECT_THROW(std::ignore = ipc::fmt(foo{}), foo);
}
TEST(fmt, byte) {
{
ipc::byte b1{}, b2(31);
EXPECT_EQ(ipc::fmt(b1), "00");
EXPECT_EQ(ipc::fmt(b2), "1f");
EXPECT_EQ(ipc::fmt(ipc::spec("03X")(b2)), "01F");
}
{
ipc::byte bs[] {31, 32, 33, 34, 35, 36, 37, 38};
EXPECT_EQ(ipc::fmt(ipc::make_span(bs)), "1f 20 21 22 23 24 25 26");
}
}
TEST(fmt, span) {
EXPECT_EQ(ipc::fmt(ipc::span<int>{}), "");
EXPECT_EQ(ipc::fmt(ipc::make_span({1, 3, 2, 4, 5, 6, 7})), "1 3 2 4 5 6 7");
}
TEST(fmt, result) {
{
ipc::result<std::uint64_t> r1;
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
ipc::result<std::uint64_t> r2(65537);
EXPECT_EQ(ipc::fmt(r2), "succ, value = 65537");
ipc::result<std::uint64_t> r3(0);
EXPECT_EQ(ipc::fmt(r3), "succ, value = 0");
}
{
ipc::result<int> r0;
EXPECT_EQ(ipc::fmt(r0), ipc::fmt(ipc::result<std::uint64_t>()));
ipc::result<int> r1 {std::error_code(-1, std::generic_category())};
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
ipc::result<void *> r2 {&r1};
EXPECT_EQ(ipc::fmt(r2), ipc::fmt("succ, value = ", (void *)&r1));
int aaa {};
ipc::result<int *> r3 {&aaa};
EXPECT_EQ(ipc::fmt(r3), ipc::fmt("succ, value = ", (void *)&aaa));
ipc::result<int *> r4 {nullptr};
EXPECT_EQ(ipc::fmt(r4), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
r4 = std::error_code(1234, std::generic_category());
EXPECT_EQ(ipc::fmt(r4), ipc::fmt("fail, error = ", std::error_code(1234, std::generic_category())));
ipc::result<int *> r5;
EXPECT_EQ(ipc::fmt(r5), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
}
{
ipc::result<std::int64_t> r1 {-123};
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("succ, value = ", -123));
}
{
ipc::result<void> r1;
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
r1 = std::error_code{};
EXPECT_TRUE(r1);
EXPECT_EQ(ipc::fmt(r1), ipc::fmt("succ, error = ", std::error_code()));
}
}

View File

@ -0,0 +1,89 @@
#include "test.h"
#include "libipc/imp/generic.h"
#include "libipc/imp/detect_plat.h"
TEST(generic, countof) {
struct {
constexpr int Size() const noexcept { return 3; }
} sv;
EXPECT_FALSE(ipc::detail_countof::trait_has_size<decltype(sv)>::value);
EXPECT_TRUE (ipc::detail_countof::trait_has_Size<decltype(sv)>::value);
std::vector<int> vec {1, 2, 3, 4, 5};
int arr[] {7, 6, 5, 4, 3, 2, 1};
auto il = {9, 7, 6, 4, 3, 1, 5};
EXPECT_EQ(ipc::countof(sv) , sv.Size());
EXPECT_EQ(ipc::countof(vec), vec.size());
EXPECT_EQ(ipc::countof(arr), sizeof(arr) / sizeof(arr[0]));
EXPECT_EQ(ipc::countof(il) , il.size());
}
TEST(generic, dataof) {
struct {
int *Data() const noexcept { return (int *)this; }
} sv;
EXPECT_FALSE(ipc::detail_dataof::trait_has_data<decltype(sv)>::value);
EXPECT_TRUE (ipc::detail_dataof::trait_has_Data<decltype(sv)>::value);
std::vector<int> vec {1, 2, 3, 4, 5};
int arr[] {7, 6, 5, 4, 3, 2, 1};
auto il = {9, 7, 6, 4, 3, 1, 5};
EXPECT_EQ(ipc::dataof(sv) , sv.Data());
EXPECT_EQ(ipc::dataof(vec), vec.data());
EXPECT_EQ(ipc::dataof(arr), arr);
EXPECT_EQ(ipc::dataof(il) , il.begin());
}
TEST(generic, horrible_cast) {
struct A {
int a_;
} a {123};
struct B {
char a_[sizeof(int)];
} b = ipc::horrible_cast<B>(a);
EXPECT_EQ(b.a_[1], 0);
EXPECT_EQ(b.a_[2], 0);
#if LIBIPC_ENDIAN_LIT
EXPECT_EQ(b.a_[0], 123);
EXPECT_EQ(b.a_[3], 0);
#else
EXPECT_EQ(b.a_[3], 123);
EXPECT_EQ(b.a_[0], 0);
#endif
#if LIBIPC_ENDIAN_LIT
EXPECT_EQ(ipc::horrible_cast<std::uint32_t>(0xff00'0000'0001ll), 1);
#else
EXPECT_EQ(ipc::horrible_cast<std::uint32_t>(0xff00'0000'0001ll), 0xff00);
#endif
}
#if defined(LIBIPC_CPP_17)
TEST(generic, in_place) {
EXPECT_TRUE((std::is_same<std::in_place_t, ipc::in_place_t>::value));
[](ipc::in_place_t) {}(std::in_place);
[](std::in_place_t) {}(ipc::in_place);
}
#endif/*LIBIPC_CPP_17*/
TEST(generic, copy_cvref) {
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int , long>, long >()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int & , long>, long & >()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int &&, long>, long &&>()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const , long>, long const >()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const & , long>, long const & >()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const &&, long>, long const &&>()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int volatile , long>, long volatile >()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int volatile & , long>, long volatile & >()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int volatile &&, long>, long volatile &&>()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const volatile , long>, long const volatile >()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const volatile & , long>, long const volatile & >()));
EXPECT_TRUE((std::is_same<ipc::copy_cvref_t<int const volatile &&, long>, long const volatile &&>()));
}

46
test/imp/test_imp_log.cpp Normal file
View File

@ -0,0 +1,46 @@
#include <iostream>
#include <string>
#include "test.h"
#include "libipc/imp/log.h"
TEST(log, logger) {
{
LIBIPC_LOG();
log.info("hello");
}
{
LIBIPC_LOG();
log.info("hello 2");
}
{
LIBIPC_LOG();
log.info("hello ", 3);
}
SUCCEED();
}
TEST(log, custom) {
struct log {
std::string i;
std::string e;
} ll_data;
auto ll = [&ll_data](auto &&ctx) {
auto s = ipc::fmt(ctx.params);
if (ctx.level == ipc::log::level::error) ll_data.e += s + " ";
else
if (ctx.level == ipc::log::level::info ) ll_data.i += s + " ";
};
LIBIPC_LOG(ll);
log.info ("hello", " world");
log.error("failed", ":");
log.info ("log", '-', "pt");
log.error("whatever");
EXPECT_EQ(ll_data.i, "hello world log-pt ");
EXPECT_EQ(ll_data.e, "failed: whatever ");
}

View File

@ -0,0 +1,67 @@
#include <sstream>
#include <cstdint>
#include "test.h"
#include "libipc/imp/result.h"
TEST(result, ok) {
ipc::result<std::uint64_t> ret;
EXPECT_FALSE(ret);
EXPECT_FALSE(ret.ok());
EXPECT_EQ(ret.value(), 0);
ret = {0};
EXPECT_TRUE(ret);
EXPECT_TRUE(ret.ok());
EXPECT_EQ(ret.value(), 0);
ret = ipc::result<std::uint64_t>(1234);
EXPECT_TRUE(ret);
EXPECT_TRUE(ret.ok());
EXPECT_EQ(ret.value(), 1234);
ret = std::error_code{9999, std::generic_category()};
EXPECT_FALSE(ret);
EXPECT_FALSE(ret.ok());
EXPECT_EQ(ret.value(), 0);
ret = 4321;
EXPECT_TRUE(ret);
EXPECT_TRUE(ret.ok());
EXPECT_EQ(ret.value(), 4321);
ipc::result<void> r1;
EXPECT_FALSE(r1);
r1 = std::error_code{};
EXPECT_TRUE(r1);
r1 = {};
EXPECT_FALSE(r1);
r1 = std::error_code{9999, std::generic_category()};
EXPECT_FALSE(r1);
EXPECT_EQ(r1.error().value(), 9999);
ipc::result<int *> r2 {nullptr, std::error_code{4321, std::generic_category()}};
EXPECT_NE(r2, nullptr); // ipc::result<int *>{nullptr}
EXPECT_EQ(*r2, nullptr);
EXPECT_FALSE(r2);
}
TEST(result, compare) {
ipc::result<std::uint64_t> r1, r2;
EXPECT_EQ(r1, r2);
ipc::result<std::uint64_t> r3(0);
EXPECT_NE(r1, r3);
ipc::result<std::uint64_t> r4(222222);
EXPECT_NE(r3, r4);
ipc::result<std::uint64_t> r5(std::error_code{9999, std::generic_category()});
EXPECT_NE(r4, r5);
EXPECT_NE(r3, r5);
r3 = r5;
EXPECT_EQ(r3, r5);
}

View File

@ -0,0 +1,66 @@
#include "test.h"
#include "libipc/imp/span.h"
#include "libipc/imp/generic.h"
#include "libipc/imp/byte.h"
TEST(span, to_address) {
int *a = new int;
EXPECT_EQ(ipc::detail_span::to_address(a), a);
std::unique_ptr<int> b {a};
EXPECT_EQ(ipc::detail_span::to_address(b), a);
}
TEST(span, span) {
auto test_proc = [](auto &&buf, auto &&sp) {
EXPECT_EQ(ipc::countof(buf), sp.size());
EXPECT_EQ(sizeof(buf[0]) * ipc::countof(buf), sp.size_bytes());
for (std::size_t i = 0; i < sp.size(); ++i) {
EXPECT_EQ(buf[i], sp[i]);
}
};
{
int buf[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto sp = ipc::make_span(buf);
test_proc(buf, sp);
test_proc(ipc::make_span({0, 1, 2}) , sp.first(3));
test_proc(ipc::make_span({6, 7, 8, 9}), sp.last(4));
test_proc(ipc::make_span({3, 4, 5, 6}), sp.subspan(3, 4));
test_proc(ipc::make_span({3, 4, 5, 6, 7, 8, 9}), sp.subspan(3));
}
{
std::vector<int> buf = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto sp = ipc::make_span(buf);
ipc::span<int> sp2 {buf.begin(), buf.end()};
EXPECT_EQ(sp, sp2);
test_proc(buf, sp);
test_proc(ipc::make_span({0, 1, 2}) , sp.first(3));
test_proc(ipc::make_span({6, 7, 8, 9}), sp.last(4));
test_proc(ipc::make_span({3, 4, 5, 6}), sp.subspan(3, 4));
test_proc(ipc::make_span({3, 4, 5, 6, 7, 8, 9}), sp.subspan(3));
test_proc(ipc::make_span((ipc::byte *)sp.data(), sp.size_bytes()), ipc::as_bytes(sp));
}
{
std::string buf = "0123456789";
auto sp = ipc::make_span(buf);
// if (sp == ipc::make_span({nullptr})) {}
test_proc(buf, sp);
test_proc(ipc::make_span("012", 3) , sp.first(3));
test_proc(ipc::make_span("6789", 4), sp.last(4));
test_proc(ipc::make_span("3456", 4), sp.subspan(3, 4));
test_proc(ipc::make_span("3456789", 7), sp.subspan(3));
}
}
TEST(span, construct) {
struct test_imp_span_construct {
/* data */
int size() const noexcept { return sizeof(this); }
auto Data() const noexcept { return this; }
} d1;
ipc::span<test_imp_span_construct const> sp {d1};
// ipc::span<int const> spp {d1};
EXPECT_EQ(sp.size(), d1.size());
EXPECT_EQ(sp.data(), d1.Data());
}

View File

@ -0,0 +1,10 @@
#include "test.h"
#include "libipc/imp/system.h"
TEST(system, conf) {
auto ret = ipc::sys::conf(ipc::sys::info::page_size);
EXPECT_TRUE(ret);
EXPECT_GE(ret.value(), 4096);
}

View File

@ -0,0 +1,46 @@
#include "test.h"
#include "libipc/imp/uninitialized.h"
TEST(uninitialized, construct) {
struct Foo {
int a_;
short b_;
char c_;
};
std::aligned_storage_t<sizeof(Foo)> foo;
Foo *pfoo = ipc::construct<Foo>(&foo, 123, short{321}, '1');
EXPECT_EQ(pfoo->a_, 123);
EXPECT_EQ(pfoo->b_, 321);
EXPECT_EQ(pfoo->c_, '1');
ipc::destroy(pfoo);
static int bar_test_flag = 0;
struct Bar : Foo {
Bar(int a, short b, char c)
: Foo{a, b, c} {
++bar_test_flag;
}
~Bar() { --bar_test_flag; }
};
std::aligned_storage_t<sizeof(Bar)> bar;
Bar *pbar = ipc::construct<Bar>(&bar, 123, short(321), '1');
EXPECT_EQ(pbar->a_, 123);
EXPECT_EQ(pbar->b_, 321);
EXPECT_EQ(pbar->c_, '1');
EXPECT_EQ(bar_test_flag, 1);
ipc::destroy(pbar);
EXPECT_EQ(bar_test_flag, 0);
std::aligned_storage_t<sizeof(Bar)> bars[3];
for (auto &b : bars) {
auto pb = ipc::construct<Bar>(&b, 321, short(123), '3');
EXPECT_EQ(pb->a_, 321);
EXPECT_EQ(pb->b_, 123);
EXPECT_EQ(pb->c_, '3');
}
//EXPECT_EQ(bar_test_flag, ipc::countof(bars));
ipc::destroy(reinterpret_cast<Bar(*)[3]>(&bars));
EXPECT_EQ(bar_test_flag, 0);
}

View File

@ -0,0 +1,32 @@
#include "test.h"
#include <utility>
#include "libipc/mem/block_pool.h"
TEST(block_pool, ctor) {
ASSERT_TRUE ((std::is_default_constructible<ipc::mem::block_pool<1, 1>>::value));
ASSERT_FALSE((std::is_copy_constructible<ipc::mem::block_pool<1, 1>>::value));
ASSERT_TRUE ((std::is_move_constructible<ipc::mem::block_pool<1, 1>>::value));
ASSERT_FALSE((std::is_copy_assignable<ipc::mem::block_pool<1, 1>>::value));
ASSERT_FALSE((std::is_move_assignable<ipc::mem::block_pool<1, 1>>::value));
}
TEST(block_pool, allocate) {
std::vector<void *> v;
ipc::mem::block_pool<1, 1> pool;
for (int i = 0; i < 100; ++i) {
v.push_back(pool.allocate());
}
for (void *p: v) {
ASSERT_FALSE(nullptr == p);
pool.deallocate(p);
}
for (int i = 0; i < 100; ++i) {
ASSERT_EQ(v[v.size() - i - 1], pool.allocate());
}
for (void *p: v) {
pool.deallocate(p);
}
}

View File

@ -0,0 +1,103 @@
#include <vector>
#include <utility>
#include "test.h"
#if defined(LIBIPC_CPP_17) && defined(__cpp_lib_memory_resource)
# include <memory_resource>
#endif
#include "libipc/mem/bytes_allocator.h"
#include "libipc/mem/memory_resource.h"
TEST(bytes_allocator, ctor) {
ipc::mem::bytes_allocator alc;
SUCCEED();
}
TEST(bytes_allocator, ctor_value_initialization) {
ipc::mem::bytes_allocator alc{};
auto p = alc.allocate(128);
EXPECT_NE(p, nullptr);
EXPECT_NO_THROW(alc.deallocate(p, 128));
}
namespace {
class dummy_resource {
public:
void *allocate(std::size_t, std::size_t = 0) noexcept {
return nullptr;
}
void deallocate(void *, std::size_t, std::size_t = 0) noexcept {
return;
}
};
} // namespace
TEST(bytes_allocator, memory_resource_traits) {
EXPECT_FALSE(ipc::mem::has_allocate<void>::value);
EXPECT_FALSE(ipc::mem::has_allocate<int>::value);
EXPECT_FALSE(ipc::mem::has_allocate<std::vector<int>>::value);
EXPECT_FALSE(ipc::mem::has_allocate<std::allocator<int>>::value);
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
EXPECT_TRUE (ipc::mem::has_allocate<std::ipc::mem::memory_resource>::value);
EXPECT_TRUE (ipc::mem::has_allocate<std::ipc::mem::container_allocator<int>>::value);
#endif
EXPECT_FALSE(ipc::mem::has_deallocate<void>::value);
EXPECT_FALSE(ipc::mem::has_deallocate<int>::value);
EXPECT_FALSE(ipc::mem::has_deallocate<std::vector<int>>::value);
EXPECT_FALSE(ipc::mem::has_deallocate<std::allocator<int>>::value);
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
EXPECT_TRUE (ipc::mem::has_deallocate<std::ipc::mem::memory_resource>::value);
EXPECT_FALSE(ipc::mem::has_deallocate<std::ipc::mem::container_allocator<int>>::value);
#endif
}
TEST(bytes_allocator, ctor_copy_move) {
ipc::mem::new_delete_resource mem_res;
dummy_resource dummy_res;
ipc::mem::bytes_allocator alc1{&mem_res}, alc2{&dummy_res};
auto p = alc1.allocate(128);
ASSERT_NE(p, nullptr);
ASSERT_NO_THROW(alc1.deallocate(p, 128));
ASSERT_EQ(alc2.allocate(128), nullptr);
ipc::mem::bytes_allocator alc3{alc1}, alc4{alc2}, alc5{std::move(alc1)};
p = alc3.allocate(128);
ASSERT_NE(p, nullptr);
ASSERT_NO_THROW(alc3.deallocate(p, 128));
ASSERT_EQ(alc4.allocate(128), nullptr);
p = alc5.allocate(128);
ASSERT_NE(p, nullptr);
ASSERT_NO_THROW(alc5.deallocate(p, 128));
}
TEST(bytes_allocator, swap) {
ipc::mem::new_delete_resource mem_res;
dummy_resource dummy_res;
ipc::mem::bytes_allocator alc1{&mem_res}, alc2{&dummy_res};
alc1.swap(alc2);
auto p = alc2.allocate(128);
ASSERT_NE(p, nullptr);
ASSERT_NO_THROW(alc2.deallocate(p, 128));
ASSERT_EQ(alc1.allocate(128), nullptr);
}
TEST(bytes_allocator, invalid_alloc_free) {
ipc::mem::bytes_allocator alc1;
EXPECT_EQ(alc1.allocate(0), nullptr);
EXPECT_NO_THROW(alc1.deallocate(nullptr, 128));
EXPECT_NO_THROW(alc1.deallocate(nullptr, 0));
EXPECT_NO_THROW(alc1.deallocate(&alc1, 0));
}
TEST(bytes_allocator, sizeof) {
EXPECT_EQ(sizeof(ipc::mem::bytes_allocator), sizeof(void *) * 2);
}

View File

@ -0,0 +1,15 @@
#include "test.h"
#include <utility>
#include "libipc/mem/central_cache_allocator.h"
TEST(central_cache_allocator, allocate) {
auto &a = ipc::mem::central_cache_allocator();
ASSERT_FALSE(nullptr == a.allocate(1));
ASSERT_FALSE(nullptr == a.allocate(10));
ASSERT_FALSE(nullptr == a.allocate(100));
ASSERT_FALSE(nullptr == a.allocate(1000));
ASSERT_FALSE(nullptr == a.allocate(10000));
}

View File

@ -0,0 +1,44 @@
#include "test.h"
#include <utility>
#include "libipc/mem/central_cache_pool.h"
TEST(central_cache_pool, ctor) {
ASSERT_FALSE((std::is_default_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
ASSERT_FALSE((std::is_copy_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
ASSERT_FALSE((std::is_move_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
ASSERT_FALSE((std::is_copy_assignable<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
ASSERT_FALSE((std::is_move_assignable<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
{
auto &pool = ipc::mem::central_cache_pool<ipc::mem::block<1024>, 1>::instance();
ipc::mem::block<1024> *b1 = pool.aqueire();
ASSERT_FALSE(nullptr == b1);
EXPECT_TRUE (nullptr == b1->next);
pool.release(b1);
ipc::mem::block<1024> *b2 = pool.aqueire();
EXPECT_EQ(b1, b2);
ipc::mem::block<1024> *b3 = pool.aqueire();
ASSERT_FALSE(nullptr == b3);
EXPECT_TRUE (nullptr == b3->next);
EXPECT_NE(b1, b3);
}
{
auto &pool = ipc::mem::central_cache_pool<ipc::mem::block<1>, 2>::instance();
ipc::mem::block<1> *b1 = pool.aqueire();
ASSERT_FALSE(nullptr == b1);
ASSERT_FALSE(nullptr == b1->next);
EXPECT_TRUE (nullptr == b1->next->next);
pool.release(b1);
ipc::mem::block<1> *b2 = pool.aqueire();
EXPECT_EQ(b1, b2);
ipc::mem::block<1> *b3 = pool.aqueire();
EXPECT_NE(b1, b3);
ipc::mem::block<1> *b4 = pool.aqueire();
ASSERT_FALSE(nullptr == b4);
ASSERT_FALSE(nullptr == b4->next);
EXPECT_TRUE (nullptr == b4->next->next);
EXPECT_NE(b1, b4);
}
}

View File

@ -0,0 +1,165 @@
#include "test.h"
#include <utility>
#include "libipc/mem/memory_resource.h"
namespace {
template <typename T>
void *test_mr(T &&mr, std::size_t bytes, std::size_t alignment) {
auto p = std::forward<T>(mr).allocate(bytes, alignment);
if (alignment == 0) {
EXPECT_EQ(p, nullptr);
} else if (p != nullptr) {
EXPECT_EQ((std::size_t)p % alignment, 0);
}
std::forward<T>(mr).deallocate(p, bytes, alignment);
return p;
}
} // namespace
TEST(memory_resource, new_delete_resource) {
ipc::mem::new_delete_resource mem_res;
EXPECT_EQ(test_mr(mem_res, 0, 0), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 1), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 2), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 3), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 8), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 64), nullptr);
EXPECT_EQ(test_mr(mem_res, 1, 0), nullptr);
EXPECT_NE(test_mr(mem_res, 1, 1), nullptr);
EXPECT_NE(test_mr(mem_res, 1, 2), nullptr);
EXPECT_EQ(test_mr(mem_res, 1, 3), nullptr);
EXPECT_NE(test_mr(mem_res, 1, 8), nullptr);
EXPECT_NE(test_mr(mem_res, 1, 64), nullptr);
}
TEST(memory_resource, monotonic_buffer_resource_construct) {
{ ipc::mem::monotonic_buffer_resource tmp; }
ipc::mem::monotonic_buffer_resource{};
ipc::mem::monotonic_buffer_resource{ipc::mem::bytes_allocator{}};
ipc::mem::monotonic_buffer_resource{0};
ipc::mem::monotonic_buffer_resource{0, ipc::mem::bytes_allocator{}};
ipc::mem::monotonic_buffer_resource{ipc::span<ipc::byte>{}};
ipc::mem::monotonic_buffer_resource{ipc::span<ipc::byte>{}, ipc::mem::bytes_allocator{}};
SUCCEED();
}
TEST(memory_resource, monotonic_buffer_resource_no_copy) {
EXPECT_FALSE(std::is_copy_constructible<ipc::mem::monotonic_buffer_resource>::value);
EXPECT_FALSE(std::is_copy_assignable<ipc::mem::monotonic_buffer_resource>::value);
EXPECT_FALSE(std::is_move_constructible<ipc::mem::monotonic_buffer_resource>::value);
EXPECT_FALSE(std::is_move_assignable<ipc::mem::monotonic_buffer_resource>::value);
}
TEST(memory_resource, monotonic_buffer_resource_upstream_resource) {
struct dummy_allocator {
bool allocated = false;
void *allocate(std::size_t, std::size_t) noexcept { allocated = true; return nullptr; }
void deallocate(void *, std::size_t, std::size_t) noexcept {}
} dummy;
ipc::mem::monotonic_buffer_resource tmp{&dummy};
ASSERT_EQ(tmp.upstream_resource().allocate(1), nullptr);
ASSERT_TRUE(dummy.allocated);
}
namespace {
struct dummy_allocator {
std::size_t allocated = 0;
void *allocate(std::size_t size, std::size_t) noexcept {
allocated += size;
return std::malloc(size);
}
void deallocate(void *p, std::size_t size, std::size_t) noexcept {
allocated -= size;
std::free(p);
}
};
} // namespace
TEST(memory_resource, monotonic_buffer_resource_allocate) {
dummy_allocator dummy;
{
ipc::mem::monotonic_buffer_resource tmp{&dummy};
ASSERT_EQ(tmp.allocate(0), nullptr);
ASSERT_EQ(dummy.allocated, 0);
}
ASSERT_EQ(dummy.allocated, 0);
{
ipc::mem::monotonic_buffer_resource tmp{&dummy};
std::size_t sz = 0;
for (std::size_t i = 1; i < 1024; ++i) {
ASSERT_NE(tmp.allocate(i), nullptr);
sz += i;
}
for (std::size_t i = 1; i < 1024; ++i) {
ASSERT_NE(tmp.allocate(1024 - i), nullptr);
sz += 1024 - i;
}
ASSERT_GE(dummy.allocated, sz);
}
ASSERT_EQ(dummy.allocated, 0);
}
TEST(memory_resource, monotonic_buffer_resource_allocate_by_buffer) {
dummy_allocator dummy;
std::array<ipc::byte, 4096> buffer;
{
ipc::mem::monotonic_buffer_resource tmp{buffer, &dummy};
for (std::size_t i = 1; i < 64; ++i) {
ASSERT_NE(tmp.allocate(i), nullptr);
}
ASSERT_EQ(dummy.allocated, 0);
std::size_t sz = 0;
for (std::size_t i = 1; i < 64; ++i) {
ASSERT_NE(tmp.allocate(64 - i), nullptr);
sz += 64 - i;
}
ASSERT_GT(dummy.allocated, sz);
}
ASSERT_EQ(dummy.allocated, 0);
}
TEST(memory_resource, monotonic_buffer_resource_release) {
dummy_allocator dummy;
{
ipc::mem::monotonic_buffer_resource tmp{&dummy};
tmp.release();
ASSERT_EQ(dummy.allocated, 0);
ASSERT_NE(tmp.allocate(1024), nullptr);
ASSERT_GE(dummy.allocated, 1024u);
ASSERT_LE(dummy.allocated, 1024u * 1.5);
tmp.release();
ASSERT_EQ(dummy.allocated, 0);
ASSERT_NE(tmp.allocate(1024), nullptr);
ASSERT_GE(dummy.allocated, 1024u);
ASSERT_LE(dummy.allocated, 1024u * 1.5);
}
ASSERT_EQ(dummy.allocated, 0);
std::array<ipc::byte, 4096> buffer;
{
ipc::mem::monotonic_buffer_resource tmp{buffer, &dummy};
auto *p = tmp.allocate(1024);
ASSERT_EQ(p, buffer.data());
ASSERT_EQ(dummy.allocated, 0);
p = tmp.allocate(10240);
ASSERT_NE(p, buffer.data());
ASSERT_LE(dummy.allocated, 10240u + 1024u);
tmp.release();
ASSERT_EQ(dummy.allocated, 0);
p = tmp.allocate(1024);
ASSERT_EQ(p, buffer.data());
ASSERT_EQ(dummy.allocated, 0);
p = tmp.allocate(10240);
ASSERT_NE(p, buffer.data());
ASSERT_LE(dummy.allocated, 10240u + 1024u);
}
ASSERT_EQ(dummy.allocated, 0);
}

172
test/mem/test_mem_new.cpp Normal file
View File

@ -0,0 +1,172 @@
#include <limits>
#include <array>
#include <cstring>
#include <cstddef>
#include <thread>
#include "test.h"
#include "libipc/mem/new.h"
TEST(new, new) {
auto p = ipc::mem::$new<int>();
ASSERT_NE(p, nullptr);
*p = -1;
ASSERT_EQ(*p, -1);
ipc::mem::$delete(p);
}
TEST(new, new_value) {
auto p = ipc::mem::$new<int>((std::numeric_limits<int>::max)());
ASSERT_NE(p, nullptr);
ASSERT_EQ(*p, (std::numeric_limits<int>::max)());
ipc::mem::$delete(p);
}
namespace {
template <std::size_t Pts, std::size_t N>
void test_new$array() {
std::array<void *, Pts> pts;
using T = std::array<char, N>;
for (int i = 0; i < (int)pts.size(); ++i) {
auto p = ipc::mem::$new<T>();
pts[i] = p;
std::memset(p, i, sizeof(T));
}
for (int i = 0; i < (int)pts.size(); ++i) {
T tmp;
std::memset(&tmp, i, sizeof(T));
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(T)), 0);
ipc::mem::$delete(static_cast<T *>(pts[i]));
}
}
} // namespace
TEST(new, new_array) {
test_new$array<1000, 10>();
test_new$array<1000, 100>();
test_new$array<1000, 1000>();
test_new$array<1000, 10000>();
test_new$array<1000, 100000>();
// test_new$array<1000, 1000000>();
}
namespace {
int construct_count__ = 0;
class Base {
public:
virtual ~Base() = default;
virtual int get() const = 0;
};
class Derived : public Base {
public:
Derived(int value) : value_(value) {
construct_count__ = value_;
}
~Derived() override {
construct_count__ = 0;
}
int get() const override { return value_; }
private:
int value_;
};
class Derived64K : public Derived {
public:
using Derived::Derived;
private:
std::array<char, 65536> padding_;
};
} // namespace
TEST(new, delete_poly) {
Base *p = ipc::mem::$new<Derived>(-1);
ASSERT_NE(p, nullptr);
ASSERT_EQ(p->get(), -1);
ASSERT_EQ(construct_count__, -1);
ipc::mem::$delete(p);
ASSERT_EQ(construct_count__, 0);
ASSERT_EQ(p, ipc::mem::$new<Derived>((std::numeric_limits<int>::max)()));
ASSERT_EQ(p->get(), (std::numeric_limits<int>::max)());
ASSERT_EQ(construct_count__, (std::numeric_limits<int>::max)());
ipc::mem::$delete(p);
ASSERT_EQ(construct_count__, 0);
}
TEST(new, delete_poly64k) {
Base *p = ipc::mem::$new<Derived64K>(-1);
ASSERT_NE(p, nullptr);
ASSERT_EQ(p->get(), -1);
ASSERT_EQ(construct_count__, -1);
ipc::mem::$delete(p);
ASSERT_EQ(construct_count__, 0);
Base *q = ipc::mem::$new<Derived64K>((std::numeric_limits<int>::max)());
ASSERT_EQ(q->get(), (std::numeric_limits<int>::max)());
ASSERT_EQ(construct_count__, (std::numeric_limits<int>::max)());
ipc::mem::$delete(q);
ASSERT_EQ(construct_count__, 0);
}
TEST(new, delete_null) {
Base *p = nullptr;
ipc::mem::$delete(p);
SUCCEED();
}
TEST(new, malloc) {
void *p = ipc::mem::$new<void>(0);
ASSERT_EQ(p, nullptr);
ipc::mem::$delete(p);
p = ipc::mem::$new<void>(1024);
ASSERT_NE(p, nullptr);
ipc::mem::$delete(p);
p = ipc::mem::$new<Derived>(-1);
ASSERT_NE(p, nullptr);
ASSERT_EQ(((Derived *)p)->get(), -1);
ASSERT_EQ(construct_count__, -1);
ipc::mem::$delete(p);
ASSERT_EQ(construct_count__, 0);
}
TEST(new, multi_thread) {
std::array<std::thread, 16> threads;
for (auto &t : threads) {
t = std::thread([] {
for (int i = 0; i < 10000; ++i) {
auto p = ipc::mem::$new<int>();
*p = i;
ipc::mem::$delete(p);
}
std::array<void *, 10000> pts;
for (int i = 0; i < 10000; ++i) {
auto p = ipc::mem::$new<std::array<char, 10>>();
pts[i] = p;
std::memset(p, i, sizeof(std::array<char, 10>));
}
for (int i = 0; i < 10000; ++i) {
std::array<char, 10> tmp;
std::memset(&tmp, i, sizeof(std::array<char, 10>));
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(std::array<char, 10>)), 0);
ipc::mem::$delete(static_cast<std::array<char, 10> *>(pts[i]));
}
});
}
for (auto &t : threads) {
t.join();
}
SUCCEED();
}

View File

@ -15,7 +15,7 @@
#include "thread_pool.h"
#include "libipc/platform/detail.h"
#ifdef IPC_OS_LINUX_
#ifdef LIBIPC_OS_LINUX
#include <fcntl.h> // ::open
#endif
@ -88,7 +88,7 @@ inline static thread_pool & reader() {
return pool;
}
#ifdef IPC_OS_LINUX_
#ifdef LIBIPC_OS_LINUX
inline bool check_exist(char const *name) noexcept {
int fd = ::open((std::string{"/dev/shm/"} + name).c_str(), O_RDONLY);
if (fd == -1) {
@ -100,7 +100,7 @@ inline bool check_exist(char const *name) noexcept {
#endif
inline bool expect_exist(char const *name, bool expected) noexcept {
#ifdef IPC_OS_LINUX_
#ifdef LIBIPC_OS_LINUX
return ipc_ut::check_exist(name) == expected;
#else
return true;

Some files were not shown because too many files have changed in this diff Show More