
#ifndef PLS_SYSTEM_DETAILS_H
#define PLS_SYSTEM_DETAILS_H

#if MSVC
#ifdef _M_X86
#define ARCH_X86
#endif
#endif

#if GCC
#ifdef __i386__
#define ARCH_X86
#endif
#endif

#include "error_handling.h"

#include <cstdint>

/**
 * Collection of system details, e.g. hardware cache line size.
 *
 * PORTABILITY:
 * Currently sane default values for x86. Must be changed for any port.
 */
namespace pls::internal::base::system_details {

/**
 * Pointer Types needed for ABA protection mixed into addresses.
 * pointer_t should be an integer type capable of holding ANY pointer value.
 */
using pointer_t = std::uintptr_t;

/**
 * Biggest type that supports atomic CAS operations.
 * Usually it is sane to assume a pointer can be swapped in a single CAS operation.
 */
using cas_integer = std::uintptr_t;
constexpr unsigned long CAS_SIZE = sizeof(cas_integer) * 8;

/**
 * Most processors have 64 byte cache lines (last 6 bit of the address are zero at line beginnings).
 */
constexpr size_t CACHE_LINE_SIZE = 64;

/**
 * Helper to align types/values on cache lines.
 */
#define PLS_CACHE_ALIGN alignas(base::system_details::CACHE_LINE_SIZE)

/**
 * Helper to find mmap page size. Either set constant or rely on system specific getter function.
 */
size_t get_page_size();

/**
 * Wrapper to create a new memory mapping.
 * Currently implemented for linux/posix systems.
 *
 * @param size The page_size aligned size of the new mapping
 * @return The newly created mapping or the error returned by the sytem call
 */
void *memory_map_range(size_t size);

/**
 * Helper to revert a previous made memory mapping.
 * currently implemented for linux/posix systems.
 *
 * @param addr The start address of the mapping.
 * @param size The size of the mapping.
 *
 * @return status code from system call
 */
int memory_unmap_range(void *addr, size_t size);

/**
 * Helper to protect a specific address range (must be mapped in the application)
 * from any access. Later accesses to this address range should result in a system error.
 *
 * @param addr The start address of the to be protected block.
 * @param size The size of the protected block.
 *
 * @return status code from system call
 */
int memory_protect_range(void *addr, size_t size);

/**
 * When spinning one wants to 'relax' the CPU from some task,
 * e.g. disabling speculative execution/branch prediction
 * or reducing its clock speed.
 * This is both good for power draw, as well as for hyperthreading.
 *
 * Choose the implementation appropriate for your compiler-cpu combination.
 */
inline void relax_cpu() {
#ifdef ARCH_X86
  asm volatile("pause":: : "memory");
#endif
}

/**
 * Prevent inlining of functions. This is a compiler specific setting and
 * it is seen as an error to not properly declare this.
 * (Some functions in the codebase MUST be re-evaluated after fiber switches,
 * by preventing inlining them we prevent the compiler caching their results)
 */
#if defined(_MSC_VER)
#define PLS_NOINLINE __declspec(noinline)
#elif defined(__GNUC__) && __GNUC__ > 3
#if defined(__CUDACC__)
#define PLS_NOINLINE __attribute__ ((noinline))
#else
#define PLS_NOINLINE __attribute__ ((__noinline__))
#endif
#else
#error "PLS requires inline prevention for certain functions."
#endif

}

#endif //PLS_SYSTEM_DETAILS_H
