#pragma once #include #include #include #include #include #include #include #include #include // macro to align variables to cache line size // There is C++ standardized value of std::hardware_destructive_interference_size. // However that currently produces a warning, probably because of concerns about ABI stability. // So instead I just hardcode a cache line size of 64 bytes for now. // The worst that could happen would be bad performance. //#define __pmq_cache_aligned alignas(std::hardware_destructive_interference_size) #define __pmq_cache_aligned alignas(64) // These #define's work for GCC and possibly other compilers. To guarantee // that these definitions are active wherever they could potentially work, I // will define them unconditionally for now, instead of guarding them with // #ifdef __GNUC__. // TODO: try on more compilers and improve compatibility logic! #if PMQ_WITH_PROFILING #define __pmq_profiled __attribute__((noinline)) // could consider attribute "noipa" instead of "noinline" #else #define __pmq_profiled #endif // "artificial" is used for small inlined wrapper methods, such as operator[]. // In theory (and to some extent in practice) the effect should be that the // code that gets inlined to a call site gets attributed to the _call site_ // instead of to the definition site of the inlined function -- reducing the // effect of jumping around like wild files when debugging. #define __pmq_artificial_method inline __attribute__((always_inline, artificial)) #define __pmq_artificial_func static inline __attribute__((always_inline, artificial)) // Attribute used for logging functions and other printf-style functions. If // these functions are properly annotated, the compiler can check matching // arguments in usage places. #define __pmq_formatter(fmt_index, first_arg_index) \ __attribute__((format(printf, (fmt_index), (first_arg_index)))) // treat format warnings as errors for the PMQ // This could be a build system flag but for now I want the change just for // this module in the larger system #pragma GCC diagnostic error "-Wformat" #ifdef NDEBUG #define pmq_assert(expr) #else static inline void __pmq_assert_fail(const char *expr, const char *file, int line, const char *func) { // this hopefully gives the logger a chance to save the logs. // If there was time, we should probably implement the logger in a separate component, // communicating using a shared memory mapping. sleep(3); __assert_fail(expr, file, line, func); } #define pmq_assert(expr) do { if (! (expr)) { __pmq_assert_fail(#expr, __FILE__, __LINE__, __func__); } } while (0) #endif __pmq_artificial_func void __pmq_assert_aligned(const void *ptr, size_t size) { assert((uintptr_t) (ptr) % size == 0); } template __pmq_artificial_func T __attribute__((aligned(size))) *__pmq_assume_aligned(const T *ptr) { __pmq_assert_aligned(ptr, size); return (T *) __builtin_assume_aligned(ptr, size); } static inline bool pmq_is_power_of_2(uint64_t value) { assert(value != 0); return (value & (value - 1)) == 0; } static inline uint64_t pmq_mask_power_of_2(uint64_t value) { assert(value != 0); assert((value & (value - 1)) == 0); return value - 1; } static inline constexpr uint64_t PMQ_Kilobytes(uint64_t count) { return count << 10; } static inline constexpr uint64_t PMQ_Megabytes(uint64_t count) { return count << 20; } static inline constexpr uint64_t PMQ_Gigabytes(uint64_t count) { return count << 30; } static inline constexpr uint64_t PMQ_Terabytes(uint64_t count) { return count << 40; } static inline constexpr uint64_t PMQ_Petabytes(uint64_t count) { return count << 50; } /* Untyped slice class. This is mainly used for slice-copy operations, both for * memory and disk I/O. It saves some boilerplate and is a little bit safer to use. * * Note, we should check if we can replace this with a standard C++ type maybe. * But I personally don't consider this code a liability, and add * __pmq_artificial_method method improves the debugging experience. */ class Untyped_Slice { void *m_data; size_t m_size; public: __pmq_artificial_method void *data() const { return m_data; } __pmq_artificial_method size_t size() const { return m_size; } __pmq_artificial_method Untyped_Slice offset_bytes(size_t offset) const { assert(offset <= m_size); return Untyped_Slice((char *) m_data + offset, m_size - offset); } __pmq_artificial_method Untyped_Slice limit_size_bytes(size_t size) const { assert(size <= m_size); return Untyped_Slice(m_data, size); } __pmq_artificial_method Untyped_Slice sub_slice_bytes(size_t offset, size_t size) const { return offset_bytes(offset).limit_size_bytes(size); } __pmq_artificial_method Untyped_Slice() { m_data = nullptr; m_size = 0; } __pmq_artificial_method Untyped_Slice(void *data, size_t size) { m_data = data; m_size = size; } }; __pmq_artificial_func void zero_out_slice(Untyped_Slice dst) { memset(dst.data(), 0, dst.size()); } __pmq_artificial_func void copy_slice(Untyped_Slice dst, Untyped_Slice src) { assert(dst.size() == src.size()); memcpy(dst.data(), src.data(), dst.size()); } __pmq_artificial_func void copy_slice_bytes(Untyped_Slice dst, Untyped_Slice src, size_t size_bytes) { assert(size_bytes <= dst.size()); assert(size_bytes <= src.size()); memcpy(dst.data(), src.data(), size_bytes); } __pmq_artificial_func void copy_to_slice(Untyped_Slice slice, const void *data, size_t size) { assert(slice.size() >= size); memcpy(slice.data(), data, size); } __pmq_artificial_func void copy_from_slice(void *data, Untyped_Slice slice, size_t size) { assert(slice.size() >= size); memcpy(data, slice.data(), size); } /* * Typed slice type. * * Note, we should check if we can replace this using std::span (C++20). */ template class Slice { T *m_data; size_t m_count; public: __pmq_artificial_method T *data() const { return m_data; } __pmq_artificial_method size_t count() const { return m_count; } __pmq_artificial_method size_t size_in_bytes() const { return m_count * sizeof (T); } __pmq_artificial_method T get(size_t index) const { assert(index < m_count); return m_data[index]; } __pmq_artificial_method T& at(size_t index) { assert(index < m_count); return m_data[index]; } __pmq_artificial_method T const& at(size_t index) const { assert(index < m_count); return m_data[index]; } __pmq_artificial_method Untyped_Slice untyped() const { return Untyped_Slice(m_data, m_count * sizeof (T)); } __pmq_artificial_method Slice slice_from(size_t start_index) { assert(start_index <= m_count); return Slice(m_data + start_index, m_count - start_index); } __pmq_artificial_method Slice slice_to(size_t count) { assert(count <= m_count); return Slice(m_data, count); } __pmq_artificial_method Slice sub_slice(size_t start_index, size_t count) { return slice_from(start_index).slice_to(count); } __pmq_artificial_method Slice() { m_data = nullptr; m_count = 0; } __pmq_artificial_method Slice(T *data, size_t count) { m_data = data; m_count = count; } }; template __pmq_artificial_func void copy_to_slice(Slice slice, const void *data, size_t size) { assert(slice.size_in_bytes() >= size); memcpy(slice.data(), data, size); } template __pmq_artificial_func void copy_from_slice(void *data, Slice slice, size_t size) { assert(slice.size_in_bytes() >= size); memcpy(data, slice.data(), size); } // A reference type, which wraps a bare pointer. The semantics are the same as // pointer but we don't allow indexing. In other words, the point of this class // is to make clear that it doesn't point to an array but only to a single // (potentially null) object. // In contrast to C++ reference types (T& value), no surprises given value // syntax but pointer semantics. template class Pointer { T *m_ptr; public: __pmq_artificial_method T *ptr() const { return m_ptr; } __pmq_artificial_method const T *const_ptr() const { return m_ptr; } __pmq_artificial_method Pointer as_const() const { return Pointer(m_ptr); } __pmq_artificial_method T *operator->() { return m_ptr; } __pmq_artificial_method Pointer(T *ptr) { assert(ptr); m_ptr = ptr; } };