#!/bin/sh
# Feature detection for the vendored aws-checksums / aws-c-common sources.
# Generates src/generated/aws/common/config.h and src/Makevars (from
# src/Makevars.in). Anything that cannot be detected falls back to the portable
# software implementation.
set -u

: "${R_HOME:=$(R RHOME)}"
CC=$("${R_HOME}/bin/R" CMD config CC)
CFLAGS=$("${R_HOME}/bin/R" CMD config CFLAGS)
CPPFLAGS=$("${R_HOME}/bin/R" CMD config CPPFLAGS)

tmp=$(mktemp -d 2>/dev/null || { d=./.configure.$$; mkdir "$d" && echo "$d"; })
trap 'rm -rf "$tmp"' EXIT

# try_compile "<flags>" <<program on stdin>>  -> exit status 0 if it compiles and links
try_compile() {
  cat > "$tmp/t.c"
  # shellcheck disable=SC2086
  $CC $CPPFLAGS $CFLAGS $1 -o "$tmp/t.out" "$tmp/t.c" >/dev/null 2>&1
}

macros=$($CC $CFLAGS -E -dM - < /dev/null 2>/dev/null)
has_macro() { echo "$macros" | grep -q "^#define $1 "; }

x86_64=no; arm64=no; windows=no; apple=no; linux=no
has_macro __x86_64__ && x86_64=yes
has_macro __aarch64__ && arm64=yes
has_macro _WIN32 && windows=yes
has_macro __APPLE__ && apple=yes
has_macro __linux__ && linux=yes

# Respect an explicit opt-out, e.g. R CMD INSTALL --configure-vars='AWSCRT_SOFTWARE_ONLY=1'
use_ext=yes
[ -n "${AWSCRT_SOFTWARE_ONLY:-}" ] && use_ext=no
# aws-c-common disables CPU extensions under MinGW (gcc bug 54412: misaligned AVX2 stack accesses).
[ "$windows" = yes ] && use_ext=no

defs=""
add_def() { defs="$defs#define $1
"; }

objs="awscrt.o init.o \
aws-checksums/source/crc32.o aws-checksums/source/crc64.o aws-checksums/source/crc_sw.o \
aws-checksums/source/crc64_sw.o aws-checksums/source/crc_util.o"
cpuid_src=aws-c-common/source/arch/generic/cpuid
impl="software"

try_compile "" <<'EOT' && add_def AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS
#include <stdbool.h>
int main(void) { int out; return __builtin_mul_overflow(1, 2, &out) ? 1 : 0; }
EOT

if [ "$x86_64" = yes ]; then
  add_def AWS_ARCH_INTEL; add_def AWS_ARCH_INTEL_X64
elif [ "$arm64" = yes ]; then
  add_def AWS_ARCH_ARM64
fi

if [ "$use_ext" = yes ] && [ "$x86_64" = yes ]; then
  # CPU features are enabled by pragmas (src/x86_target.h), not -m flags, which R CMD check
  # flags as non-portable. Probe with the same pragma pattern as the build.
  PRAGMA_BEGIN='#if defined(__clang__)
#  pragma clang attribute push(__attribute__((target("avx512f,avx512vl,vpclmulqdq,pclmul,avx2,avx,sse4.2"))), apply_to = function)
#elif defined(__GNUC__)
#  pragma GCC push_options
#  pragma GCC target("avx512f,avx512vl,vpclmulqdq,pclmul,avx2,avx,sse4.2")
#endif
'
  PRAGMA_END='
#if defined(__clang__)
#  pragma clang attribute pop
#elif defined(__GNUC__)
#  pragma GCC pop_options
#endif
'
  # try_x86 "<headers>" "<body>": compile a function that uses intrinsics under the pragma.
  try_x86() {
    printf '%s\n%s\n%s\n%s\n' "$1" "$PRAGMA_BEGIN" "$2" "$PRAGMA_END" > "$tmp/x.c"
    printf 'int main(void) { return 0; }\n' >> "$tmp/x.c"
    $CC $CPPFLAGS $CFLAGS -o "$tmp/x.out" "$tmp/x.c" >/dev/null 2>&1
  }

  have_asm=no
  try_compile "" <<'EOT' && have_asm=yes
int main(void) { int foo = 42, bar = 24; __asm__ __volatile__("":"=r"(foo):"r"(bar):"memory"); return foo; }
EOT

  [ "$have_asm" = yes ] && add_def AWS_HAVE_GCC_INLINE_ASM
  have_avx2=no; have_avx512=no; have_clmul=no
  try_x86 '#include <immintrin.h>
#include <emmintrin.h>
#include <string.h>' 'int f(void) { __m256i vec; memset(&vec, 0, sizeof(vec)); _mm256_shuffle_epi8(vec, vec);
  _mm256_set_epi32(1,2,3,4,5,6,7,8); _mm256_permutevar8x32_epi32(vec, vec); return 0; }' && have_avx2=yes
  try_x86 '#include <immintrin.h>' 'int f(void) { __m512 a = _mm512_setzero_ps(); (void)a; return 0; }' && have_avx512=yes
  try_x86 '#include <wmmintrin.h>
#include <emmintrin.h>' 'int f(void) { __m128i a = _mm_setzero_si128(); __m128i b = _mm_setzero_si128();
  __m128i r = _mm_clmulepi64_si128(a, b, 0x00); (void)r; return 0; }' && have_clmul=yes
  [ "$have_avx2" = yes ] && try_x86 '#include <immintrin.h>
#include <string.h>' 'int f(void) { __m256i vec; memset(&vec, 0, sizeof(vec)); return (int)_mm256_extract_epi64(vec, 2); }' && add_def AWS_HAVE_MM256_EXTRACT_EPI64
  [ "$have_avx2" = yes ] && add_def AWS_HAVE_AVX2_INTRINSICS
  [ "$have_avx512" = yes ] && add_def AWS_HAVE_AVX512_INTRINSICS
  [ "$have_clmul" = yes ] && add_def AWS_HAVE_CLMUL

  add_def AWS_USE_CPU_EXTENSIONS
  impl="x86_64 (SSE4.2"
  cpuid_src=aws-c-common/source/arch/intel/cpuid
  objs="$objs aws-c-common/source/arch/intel/asm/cpuid.o"
  p=aws-checksums/source/intel
  [ "$have_asm" = yes ] && objs="$objs $p/asm/crc32c_sse42_asm.o"
  if [ "$have_avx512" = yes ]; then
    objs="$objs x86_crc64_avx512.o"
    impl="$impl, AVX-512"
  fi
  [ "$have_clmul" = yes ] && impl="$impl, PCLMULQDQ"
  objs="$objs x86_crc32c_avx512.o"
  [ "$have_clmul" = yes ] && objs="$objs x86_crc64_clmul.o"
  impl="$impl)"

elif [ "$use_ext" = yes ] && [ "$arm64" = yes ] && { [ "$apple" = yes ] || [ "$linux" = yes ]; }; then
  # Probe with the same pragma pattern as arm_crc.c (not -march=), so toolchains whose
  # arm_neon.h breaks under the pragma (e.g. Apple clang 16) fall back to software.
  if try_compile "" <<'EOT'
#if defined(__clang__)
#    pragma clang attribute push(__attribute__((target("crc,crypto"))), apply_to = function)
#elif defined(__GNUC__)
#    pragma GCC push_options
#    pragma GCC target("+crc+crypto")
#endif
#include <arm_acle.h>
#include <arm_neon.h>
int main(void) {
  int crc = __crc32d(0, 1);
  poly64x2_t v = vdupq_n_p64(1);
  poly128_t r = vmull_high_p64(v, v);
  (void)r;
  return crc;
}
#if defined(__clang__)
#    pragma clang attribute pop
#elif defined(__GNUC__)
#    pragma GCC pop_options
#endif
EOT
  then
    add_def AWS_HAVE_ARMv8_1
    add_def AWS_USE_CPU_EXTENSIONS
    if [ "$apple" = yes ]; then cpuid_src=aws-c-common/source/arch/arm/darwin/cpuid
    else cpuid_src=aws-c-common/source/arch/arm/auxv/cpuid; fi
    p=aws-checksums/source/arm
    # CPU features are enabled by pragmas in arm_crc.c; -march= would trip R CMD check.
    objs="$objs arm_crc.o"
    impl="arm64 (CRC, PMULL)"
  fi
fi
objs="$objs $cpuid_src.o"

mkdir -p src/generated/aws/common
{
  echo "/* Generated by ./configure; do not edit. */"
  echo "#ifndef AWS_COMMON_CONFIG_H"
  echo "#define AWS_COMMON_CONFIG_H"
  printf "%s" "$defs"
  echo "#endif"
} > src/generated/aws/common/config.h

vis=""
if [ "$windows" = no ]; then
  try_compile "-fvisibility=hidden" <<'EOT' && vis="-fvisibility=hidden"
int main(void) { return 0; }
EOT
fi

sed -e "s|@OBJECTS@|$objs|" -e "s|@VISIBILITY@|$vis|" src/Makevars.in > src/Makevars

echo "awscrt: using $impl implementation"
