# Copyright (c) 2023-present The Limenka developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.

include(CheckCXXSourceCompiles)
include(CheckCXXSymbolExists)
include(CheckIncludeFileCXX)

# The following HAVE_{HEADER}_H variables go to the limenka-build-config.h header.
check_include_file_cxx(sys/prctl.h HAVE_SYS_PRCTL_H)
check_include_file_cxx(sys/resources.h HAVE_SYS_RESOURCES_H)
check_include_file_cxx(sys/vmmeter.h HAVE_SYS_VMMETER_H)
check_include_file_cxx(vm/vm_param.h HAVE_VM_VM_PARAM_H)

check_cxx_symbol_exists(O_CLOEXEC "fcntl.h" HAVE_O_CLOEXEC)
check_cxx_symbol_exists(fdatasync "unistd.h" HAVE_FDATASYNC)
check_cxx_symbol_exists(fork "unistd.h" HAVE_DECL_FORK)
check_cxx_symbol_exists(pipe2 "unistd.h" HAVE_DECL_PIPE2)
check_cxx_symbol_exists(setsid "unistd.h" HAVE_DECL_SETSID)

check_include_file_cxx(sys/types.h HAVE_SYS_TYPES_H)
check_include_file_cxx(ifaddrs.h HAVE_IFADDRS_H)
if(HAVE_SYS_TYPES_H AND HAVE_IFADDRS_H)
  include(TestAppendRequiredLibraries)
  test_append_socket_library(core_interface)
endif()

include(TestAppendRequiredLibraries)
test_append_atomic_library(core_interface)

check_cxx_symbol_exists(std::system "cstdlib" HAVE_STD_SYSTEM)
check_cxx_symbol_exists(::_wsystem "stdlib.h" HAVE__WSYSTEM)
if(HAVE_STD_SYSTEM OR HAVE__WSYSTEM)
  set(HAVE_SYSTEM 1)
endif()

check_cxx_source_compiles("
  #include <string.h>

  int main()
  {
    char buf[100];
    char* p{strerror_r(0, buf, sizeof buf)};
    (void)p;
  }
  " STRERROR_R_CHAR_P
)

# Check for malloc_info (for memory statistics information in getmemoryinfo).
check_cxx_symbol_exists(malloc_info "malloc.h" HAVE_MALLOC_INFO)

# Check for mallopt(M_ARENA_MAX) (to set glibc arenas).
check_cxx_source_compiles("
  #include <malloc.h>

  int main()
  {
    mallopt(M_ARENA_MAX, 1);
  }
  " HAVE_MALLOPT_ARENA_MAX
)

# Check for posix_fallocate().
check_cxx_source_compiles("
  // same as in src/util/fs_helpers.cpp
  #ifdef __linux__
  #ifdef _POSIX_C_SOURCE
  #undef _POSIX_C_SOURCE
  #endif
  #define _POSIX_C_SOURCE 200112L
  #endif // __linux__
  #include <fcntl.h>

  int main()
  {
    return posix_fallocate(0, 0, 0);
  }
  " HAVE_POSIX_FALLOCATE
)

# Check for strong getauxval() support in the system headers.
check_cxx_source_compiles("
  #include <sys/auxv.h>

  int main()
  {
    getauxval(AT_HWCAP);
  }
  " HAVE_STRONG_GETAUXVAL
)

# Check for UNIX sockets.
check_cxx_source_compiles("
  #include <sys/socket.h>
  #include <sys/un.h>

  int main()
  {
    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
  }
  " HAVE_SOCKADDR_UN
)

# Check for close_range().
# NOTE: At least FreeBSD supports the generic variant.
check_cxx_source_compiles("
  // same as in src/util/subprocess.cpp
  #include <limits.h>
  #include <unistd.h>

  int main()
  {
    return close_range(3, UINT_MAX, 0);
  }
  " HAVE_CLOSE_RANGE_GENERIC
)
if(NOT HAVE_CLOSE_RANGE_GENERIC)
  check_cxx_source_compiles("
    // same as in src/util/subprocess.cpp
    #define _GNU_SOURCE
    #include <linux/close_range.h>
    #include <limits.h>
    #include <unistd.h>

    int main()
    {
      return close_range(3, UINT_MAX, 0);
    }
    " HAVE_CLOSE_RANGE_LINUX
  )
endif()

# Check for different ways of gathering OS randomness:
# - Linux getrandom()
check_cxx_source_compiles("
  #include <sys/random.h>

  int main()
  {
    getrandom(nullptr, 32, 0);
  }
  " HAVE_GETRANDOM
)

# - BSD getentropy()
check_cxx_source_compiles("
  #include <sys/random.h>

  int main()
  {
    getentropy(nullptr, 32);
  }
  " HAVE_GETENTROPY_RAND
)


# - BSD sysctl()
check_cxx_source_compiles("
  #include <sys/types.h>
  #include <sys/sysctl.h>

  #ifdef __linux__
  #error Don't use sysctl on Linux, it's deprecated even when it works
  #endif

  int main()
  {
    sysctl(nullptr, 2, nullptr, nullptr, nullptr, 0);
  }
  " HAVE_SYSCTL
)

# - BSD sysctl(KERN_ARND)
check_cxx_source_compiles("
  #include <sys/types.h>
  #include <sys/sysctl.h>

  #ifdef __linux__
  #error Don't use sysctl on Linux, it's deprecated even when it works
  #endif

  int main()
  {
    static int name[2] = {CTL_KERN, KERN_ARND};
    sysctl(name, 2, nullptr, nullptr, nullptr, 0);
  }
  " HAVE_SYSCTL_ARND
)

if(NOT MSVC)
  include(CheckSourceCompilesWithFlags)

  # Check for SSE4.1 intrinsics.
  set(SSE41_CXXFLAGS -msse4.1)
  check_cxx_source_compiles_with_flags([[
    #include <immintrin.h>

    #if defined(__clang__)
    #pragma clang attribute push(__attribute__((__target__("sse4.1"))), apply_to = function)
    __attribute__((__target__("arch=skylake-avx512")))
    #elif defined(__GNUC__)
    #pragma GCC target ("sse4.1")
    #endif

    int main()
    {
      __m128i a = _mm_set1_epi32(0);
      __m128i b = _mm_set1_epi32(1);
      __m128i r = _mm_blend_epi16(a, b, 0xFF);
      return _mm_extract_epi32(r, 3);
    }

    #if defined(__clang__)
    #pragma clang attribute pop
    #endif
    ]] HAVE_SSE41
    CXXFLAGS ${SSE41_CXXFLAGS}
  )
  set(ENABLE_SSE41 ${HAVE_SSE41})

  # Check for AVX2 intrinsics.
  set(AVX2_CXXFLAGS -mavx -mavx2)
  check_cxx_source_compiles_with_flags([[
    #include <immintrin.h>

    #if defined(__clang__)
    #pragma clang attribute push(__attribute__((__target__("avx,avx2"))), apply_to = function)
    #elif defined(__GNUC__)
    #pragma GCC target ("avx,avx2")
    #endif

    int main()
    {
      __m256i l = _mm256_set1_epi32(0);
      return _mm256_extract_epi32(l, 7);
    }

    #if defined(__clang__)
    #pragma clang attribute pop
    #endif
    ]] HAVE_AVX2
    CXXFLAGS ${AVX2_CXXFLAGS}
  )
  set(ENABLE_AVX2 ${HAVE_AVX2})

  # Check for x86 SHA-NI intrinsics.
  set(X86_SHANI_CXXFLAGS -msse4 -msha)
  check_cxx_source_compiles_with_flags([[
    #include <immintrin.h>

    #if defined(__clang__)
    #pragma clang attribute push(__attribute__((__target__("sse4,sse4.1,sha"))), apply_to = function)
    #elif defined(__GNUC__)
    #pragma GCC target ("sse4,sse4.1,sha")
    #endif

    int main()
    {
      __m128i i = _mm_set1_epi32(0);
      __m128i j = _mm_set1_epi32(1);
      __m128i k = _mm_set1_epi32(2);
      return _mm_extract_epi32(_mm_sha256rnds2_epu32(i, j, k), 0);
    }

    #if defined(__clang__)
    #pragma clang attribute pop
    #endif
    ]] HAVE_X86_SHANI
    CXXFLAGS ${X86_SHANI_CXXFLAGS}
  )
  set(ENABLE_X86_SHANI ${HAVE_X86_SHANI})

  # Check for ARMv8 SHA-NI intrinsics.
  set(ARM_SHANI_CXXFLAGS -march=armv8-a+crypto)
  check_cxx_source_compiles_with_flags([[
    #include <arm_neon.h>

    #if defined(__clang__)
    #pragma clang attribute push(__attribute__((__target__("armv8-a+crypto"))), apply_to = function)
    #elif defined(__GNUC__)
    #pragma GCC target ("armv8-a+crypto")
    #endif

    int main()
    {
      uint32x4_t a, b, c;
      vsha256h2q_u32(a, b, c);
      vsha256hq_u32(a, b, c);
      vsha256su0q_u32(a, b);
      vsha256su1q_u32(a, b, c);
    }

    #if defined(__clang__)
    #pragma clang attribute pop
    #endif
    ]] HAVE_ARM_SHANI
    CXXFLAGS ${ARM_SHANI_CXXFLAGS}
  )
  set(ENABLE_ARM_SHANI ${HAVE_ARM_SHANI})

  # Check for POWER8 vector intrinsics.
  function(check_power8_vector VAR)
    check_cxx_source_compiles_with_flags([[
      #include <altivec.h>
      #include <stdint.h>

      int main()
      {
        unsigned char src[16];
        __builtin_crypto_vshasigmaw((__vector uint32_t)vec_vsx_ld(0, src), 1, 0xf);
        return 0;
      }
      ]] ${VAR}
      CXXFLAGS ${POWER8_CXXFLAGS}
    )
  endfunction()
  set(POWER8_CXXFLAGS -mpower8-vector)
  check_power8_vector(HAVE_POWER8_VECTOR)
  set(ENABLE_POWER8 ${HAVE_POWER8_VECTOR})
  if(NOT HAVE_POWER8_VECTOR)
    set(POWER8_CXXFLAGS -mcpu=power8)
    check_power8_vector(HAVE_MCPU_POWER8_FOR_VECTOR)
    set(ENABLE_POWER8 ${HAVE_MCPU_POWER8_FOR_VECTOR})
  endif()
endif()
