// Copyright 2020 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef HIGHWAY_HWY_DETECT_COMPILER_ARCH_H_
#define HIGHWAY_HWY_DETECT_COMPILER_ARCH_H_

// Detects compiler and arch from predefined macros. Zero dependencies for
// inclusion by foreach_target.h.

// Add to #if conditions to prevent IDE from graying out code.
// Note for clangd users: There is no predefined macro in clangd, so you must
// manually add these two lines (without the preceding '// ') to your project's
// `.clangd` file:
// CompileFlags:
//   Add: [-D__CLANGD__]
#if (defined __CDT_PARSER__) || (defined __INTELLISENSE__) || \
    (defined Q_CREATOR_RUN) || (defined __CLANGD__) ||        \
    (defined GROK_ELLIPSIS_BUILD) || (defined __JETBRAINS_IDE__)
#define HWY_IDE 1
#else
#define HWY_IDE 0
#endif

//------------------------------------------------------------------------------
// Compiler

// Actual MSVC, not clang-cl, which defines _MSC_VER but doesn't behave like
// MSVC in other aspects (e.g. HWY_DIAGNOSTICS).
#if defined(_MSC_VER) && !defined(__clang__)
#define HWY_COMPILER_MSVC _MSC_VER
#else
#define HWY_COMPILER_MSVC 0
#endif

#if defined(_MSC_VER) && defined(__clang__)
#define HWY_COMPILER_CLANGCL _MSC_VER
#else
#define HWY_COMPILER_CLANGCL 0
#endif

#ifdef __INTEL_COMPILER
#define HWY_COMPILER_ICC __INTEL_COMPILER
#else
#define HWY_COMPILER_ICC 0
#endif

#ifdef __INTEL_LLVM_COMPILER
#define HWY_COMPILER_ICX __INTEL_LLVM_COMPILER
#else
#define HWY_COMPILER_ICX 0
#endif

// HWY_COMPILER_GCC is a generic macro for all compilers implementing the GNU
// compiler extensions (eg. Clang, Intel...)
#ifdef __GNUC__
#define HWY_COMPILER_GCC (__GNUC__ * 100 + __GNUC_MINOR__)
#else
#define HWY_COMPILER_GCC 0
#endif

#ifndef HWY_COMPILER_CLANG  // Allow user override.
#ifdef __clang__            // Clang or clang-cl, not GCC.
// In case of Apple LLVM (whose version number is unrelated to that of LLVM) or
// an invalid version number, deduce it from the presence of warnings.
// Originally based on
// https://github.com/simd-everywhere/simde/blob/47d6e603de9d04ee05cdfbc57cf282a02be1bf2a/simde/simde-detect-clang.h#L59.
// Please send updates below to them as well, thanks!
#if defined(__apple_build_version__) || __clang_major__ >= 999
#if __has_builtin(__builtin_elementwise_fshl)
#define HWY_COMPILER_CLANG 2201
#elif __has_builtin(__builtin_structured_binding_size)
#define HWY_COMPILER_CLANG 2101
#elif __has_builtin(__builtin_common_type)
#define HWY_COMPILER_CLANG 2001
#elif __has_warning("-Wreturn-mismatch")
#define HWY_COMPILER_CLANG 1901
#elif __has_warning("-Woverriding-option")
#define HWY_COMPILER_CLANG 1801
// No new warnings in 17.0, and Apple LLVM 15.3, which should be 1600, already
// has the unsafe_buffer_usage attribute, so we instead check for new builtins.
#elif __has_builtin(__builtin_nondeterministic_value)
#define HWY_COMPILER_CLANG 1700
#elif __has_attribute(nouwtable)  // no new warnings in 16.0
#define HWY_COMPILER_CLANG 1600
#elif __has_warning("-Warray-parameter")
#define HWY_COMPILER_CLANG 1500
#elif __has_warning("-Wbitwise-instead-of-logical")
#define HWY_COMPILER_CLANG 1400
#elif __has_warning("-Wreserved-identifier")
#define HWY_COMPILER_CLANG 1300
#elif __has_warning("-Wformat-insufficient-args")
#define HWY_COMPILER_CLANG 1200
#elif __has_warning("-Wimplicit-const-int-float-conversion")
#define HWY_COMPILER_CLANG 1100
#elif __has_warning("-Wmisleading-indentation")
#define HWY_COMPILER_CLANG 1000
#elif defined(__FILE_NAME__)
#define HWY_COMPILER_CLANG 900
#elif __has_warning("-Wextra-semi-stmt") || \
    __has_builtin(__builtin_rotateleft32)
#define HWY_COMPILER_CLANG 800
// For reasons unknown, XCode 10.3 (Apple LLVM version 10.0.1) is apparently
// based on Clang 7, but does not support the warning we test.
// See https://en.wikipedia.org/wiki/Xcode#Toolchain_versions and
// https://trac.macports.org/wiki/XcodeVersionInfo.
#elif __has_warning("-Wc++98-compat-extra-semi") || \
    (defined(__apple_build_version__) && __apple_build_version__ >= 10010000)
#define HWY_COMPILER_CLANG 700
#else  // Anything older than 7.0 is not recommended for Highway.
#define HWY_COMPILER_CLANG 600
#endif  // __has_warning chain
#else  // use normal version
#define HWY_COMPILER_CLANG (__clang_major__ * 100 + __clang_minor__)
#define HWY_COMPILER3_CLANG \
  (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__)
#endif
#else  // Not clang
#define HWY_COMPILER_CLANG 0
#define HWY_COMPILER3_CLANG 0
#endif  // __clang__
#endif  // HWY_COMPILER_CLANG

// User-defined or deduced HWY_COMPILER_CLANG: derive HWY_COMPILER3_CLANG.
#ifndef HWY_COMPILER3_CLANG
#define HWY_COMPILER3_CLANG (HWY_COMPILER_CLANG * 100)
#endif

#if HWY_COMPILER_GCC && !HWY_COMPILER_CLANG && !HWY_COMPILER_ICC && \
    !HWY_COMPILER_ICX
#define HWY_COMPILER_GCC_ACTUAL HWY_COMPILER_GCC
#else
#define HWY_COMPILER_GCC_ACTUAL 0
#endif

// More than one may be nonzero, but we want at least one.
#if 0 == (HWY_COMPILER_MSVC + HWY_COMPILER_CLANGCL + HWY_COMPILER_ICC + \
          HWY_COMPILER_ICX + HWY_COMPILER_GCC + HWY_COMPILER_CLANG)
#error "Unsupported compiler"
#endif

// We should only detect one of these (only clang/clangcl/icx overlap)
#if 1 < (!!HWY_COMPILER_MSVC + (!!HWY_COMPILER_ICC & !HWY_COMPILER_ICX) + \
         !!HWY_COMPILER_GCC_ACTUAL +                                      \
         !!(HWY_COMPILER_ICX | HWY_COMPILER_CLANGCL | HWY_COMPILER_CLANG))
#error "Detected multiple compilers"
#endif

//------------------------------------------------------------------------------
// Compiler features and C++ version

#ifdef __has_builtin
#define HWY_HAS_BUILTIN(name) __has_builtin(name)
#else
#define HWY_HAS_BUILTIN(name) 0
#endif

#ifdef __has_attribute
#define HWY_HAS_ATTRIBUTE(name) __has_attribute(name)
#else
#define HWY_HAS_ATTRIBUTE(name) 0
#endif

#ifdef __has_cpp_attribute
#define HWY_HAS_CPP_ATTRIBUTE(name) __has_cpp_attribute(name)
#else
#define HWY_HAS_CPP_ATTRIBUTE(name) 0
#endif

#ifdef __has_feature
#define HWY_HAS_FEATURE(name) __has_feature(name)
#else
#define HWY_HAS_FEATURE(name) 0
#endif

// NOTE: clang ~17 does not correctly handle wrapping __has_include in a macro.

#if HWY_COMPILER_MSVC && defined(_MSVC_LANG) && _MSVC_LANG > __cplusplus
#define HWY_CXX_LANG _MSVC_LANG
#else
#define HWY_CXX_LANG __cplusplus
#endif

// Use instead of constexpr to avoid compiler errors for older compilers. This
// macro is for when a constexpr function involves multiple statements and
// loops, which is allowed in C++14 but not before. If the compiler does not
// support C++14 constexpr, this evaluates to nothing.
#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304L
#define HWY_CXX14_CONSTEXPR constexpr
#else
#define HWY_CXX14_CONSTEXPR
#endif

// Same as above, but for C++17 constexpr, which adds support for lambdas, the
// standard library, and capturing *this.
// Note that C++17 constexpr still disallows allocating and virtual functions,
// which are allowed in C++20, but we do not have a use case yet.
#if defined(__cpp_constexpr) && __cpp_constexpr >= 201603L
#define HWY_CXX17_CONSTEXPR constexpr
#else
#define HWY_CXX17_CONSTEXPR
#endif

// Use instead of `if constexpr` to avoid compiler errors for older compilers.
// When compilers lack C++17 support, this evaluates to a normal if statement.
#if HWY_CXX_LANG >= 201703L || \
    (defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L)
#define HWY_IF_CONSTEXPR if constexpr
#else
#define HWY_IF_CONSTEXPR if
#endif

// Use for constexpr variables at namespace scope in headers. Constexpr is
// separate to allow using `HWY_CXX14_CONSTEXPR` if required.
#ifndef HWY_INLINE_VAR
#if __cplusplus > 201402L
// C++17: mark as COMDAT to ensure linkers de-duplicate it. See
// https://quuxplusone.github.io/blog/2022/07/08/inline-constexpr/
#define HWY_INLINE_VAR inline
#else
#define HWY_INLINE_VAR
#endif
#endif

//------------------------------------------------------------------------------
// Sanitizers

#if HWY_HAS_FEATURE(memory_sanitizer) || defined(MEMORY_SANITIZER) || \
    defined(__SANITIZE_MEMORY__)
#define HWY_IS_MSAN 1
#else
#define HWY_IS_MSAN 0
#endif

#if HWY_HAS_FEATURE(address_sanitizer) || defined(ADDRESS_SANITIZER) || \
    defined(__SANITIZE_ADDRESS__)
#define HWY_IS_ASAN 1
#else
#define HWY_IS_ASAN 0
#endif

#if HWY_HAS_FEATURE(hwaddress_sanitizer) || defined(HWADDRESS_SANITIZER) || \
    defined(__SANITIZE_HWADDRESS__)
#define HWY_IS_HWASAN 1
#else
#define HWY_IS_HWASAN 0
#endif

#if HWY_HAS_FEATURE(thread_sanitizer) || defined(THREAD_SANITIZER) || \
    defined(__SANITIZE_THREAD__)
#define HWY_IS_TSAN 1
#else
#define HWY_IS_TSAN 0
#endif

#if HWY_HAS_FEATURE(undefined_behavior_sanitizer) || \
    defined(UNDEFINED_BEHAVIOR_SANITIZER)
#define HWY_IS_UBSAN 1
#else
#define HWY_IS_UBSAN 0
#endif

// MSAN may cause lengthy build times or false positives e.g. in AVX3 DemoteTo.
// You can disable MSAN by adding this attribute to the function that fails.
#if HWY_IS_MSAN
#define HWY_ATTR_NO_MSAN __attribute__((no_sanitize_memory))
#else
#define HWY_ATTR_NO_MSAN
#endif

#if HWY_IS_ASAN || HWY_IS_HWASAN || HWY_IS_MSAN || HWY_IS_TSAN || HWY_IS_UBSAN
#define HWY_IS_SANITIZER 1
#else
#define HWY_IS_SANITIZER 0
#endif

//------------------------------------------------------------------------------
// Architecture

#if defined(__i386__) || defined(_M_IX86)
#define HWY_ARCH_X86_32 1
#else
#define HWY_ARCH_X86_32 0
#endif

#if defined(__x86_64__) || defined(_M_X64)
#define HWY_ARCH_X86_64 1
#else
#define HWY_ARCH_X86_64 0
#endif

#if HWY_ARCH_X86_32 && HWY_ARCH_X86_64
#error "Cannot have both x86-32 and x86-64"
#endif

#if HWY_ARCH_X86_32 || HWY_ARCH_X86_64
#define HWY_ARCH_X86 1
#else
#define HWY_ARCH_X86 0
#endif

#if defined(__powerpc64__) || defined(_M_PPC) || defined(__powerpc__)
#define HWY_ARCH_PPC 1
#else
#define HWY_ARCH_PPC 0
#endif

#if defined(__powerpc64__) || (HWY_ARCH_PPC && defined(__64BIT__))
#define HWY_ARCH_PPC_64 1
#else
#define HWY_ARCH_PPC_64 0
#endif

// aarch32 is currently not supported; please raise an issue if you want it.
#if defined(__ARM_ARCH_ISA_A64) || defined(__aarch64__) || defined(_M_ARM64)
#define HWY_ARCH_ARM_A64 1
#else
#define HWY_ARCH_ARM_A64 0
#endif

#if (defined(__ARM_ARCH) && __ARM_ARCH == 7) || (defined(_M_ARM) && _M_ARM == 7)
#define HWY_ARCH_ARM_V7 1
#else
#define HWY_ARCH_ARM_V7 0
#endif

#if HWY_ARCH_ARM_A64 && HWY_ARCH_ARM_V7
#error "Cannot have both A64 and V7"
#endif

// Any *supported* version of Arm, i.e. 7 or later
#if HWY_ARCH_ARM_A64 || HWY_ARCH_ARM_V7
#define HWY_ARCH_ARM 1
#else
#define HWY_ARCH_ARM 0
#endif

// Older than Armv7 (e.g. armel aka Armv5) => we do not support SIMD.
#if (defined(__arm__) || defined(_M_ARM)) && !HWY_ARCH_ARM
#define HWY_ARCH_ARM_OLD 1
#else
#define HWY_ARCH_ARM_OLD 0
#endif

#if defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__WASM__)
#define HWY_ARCH_WASM 1
#else
#define HWY_ARCH_WASM 0
#endif

#ifdef __riscv
#define HWY_ARCH_RISCV 1
#else
#define HWY_ARCH_RISCV 0
#endif
// DEPRECATED names; please use HWY_ARCH_RISCV instead.
#define HWY_ARCH_RVV HWY_ARCH_RISCV

#if HWY_ARCH_RISCV && defined(__riscv_xlen)

#if __riscv_xlen == 32
#define HWY_ARCH_RISCV_32 1
#else
#define HWY_ARCH_RISCV_32 0
#endif

#if __riscv_xlen == 64
#define HWY_ARCH_RISCV_64 1
#else
#define HWY_ARCH_RISCV_64 0
#endif

#else  // !HWY_ARCH_RISCV || !defined(__riscv_xlen)
#define HWY_ARCH_RISCV_32 0
#define HWY_ARCH_RISCV_64 0
#endif  // HWY_ARCH_RISCV && defined(__riscv_xlen)

#if HWY_ARCH_RISCV_32 && HWY_ARCH_RISCV_64
#error "Cannot have both RISCV_32 and RISCV_64"
#endif

#if defined(__s390x__)
#define HWY_ARCH_S390X 1
#else
#define HWY_ARCH_S390X 0
#endif

#if defined(__loongarch64__) || defined(__loongarch64) || \
    (defined(__loongarch_grlen) && __loongarch_grlen == 64)
#define HWY_ARCH_LOONGARCH_64 1
#else
#define HWY_ARCH_LOONGARCH_64 0
#endif

#if defined(__loongarch__) && !HWY_ARCH_LOONGARCH_64
#define HWY_ARCH_LOONGARCH_32 1
#else
#define HWY_ARCH_LOONGARCH_32 0
#endif

#if HWY_ARCH_LOONGARCH_64 || HWY_ARCH_LOONGARCH_32
#define HWY_ARCH_LOONGARCH 1
#else
#define HWY_ARCH_LOONGARCH 0
#endif

#if defined(__hexagon__) || defined(__HEXAGON_ARCH__)
#define HWY_ARCH_HEXAGON 1
#else
#define HWY_ARCH_HEXAGON 0
#endif

// It is an error to detect multiple architectures at the same time, but OK to
// detect none of the above.
#if (HWY_ARCH_X86 + HWY_ARCH_PPC + HWY_ARCH_ARM + HWY_ARCH_ARM_OLD +        \
     HWY_ARCH_WASM + HWY_ARCH_RISCV + HWY_ARCH_S390X + HWY_ARCH_LOONGARCH + \
     HWY_ARCH_HEXAGON) > 1
#error "Must not detect more than one architecture"
#endif

#if HWY_ARCH_RISCV
#define HWY_ARCH_MAX_BYTES 65536
#elif HWY_ARCH_ARM_A64
#define HWY_ARCH_MAX_BYTES 256
#elif HWY_ARCH_HEXAGON
#define HWY_ARCH_MAX_BYTES 128
#elif HWY_ARCH_X86
#define HWY_ARCH_MAX_BYTES 64
#elif HWY_ARCH_WASM || HWY_ARCH_LOONGARCH
#define HWY_ARCH_MAX_BYTES 32
#elif HWY_ARCH_PPC || HWY_ARCH_S390X || HWY_ARCH_ARM_V7 || HWY_ARCH_ARM_OLD
#define HWY_ARCH_MAX_BYTES 16
#endif

//------------------------------------------------------------------------------
// Operating system

#if defined(_WIN32) || defined(_WIN64)
#define HWY_OS_WIN 1
#else
#define HWY_OS_WIN 0
#endif

#if defined(linux) || defined(__linux__)
#define HWY_OS_LINUX 1
#else
#define HWY_OS_LINUX 0
#endif

// iOS or Mac
#if defined(__APPLE__)
#define HWY_OS_APPLE 1
#else
#define HWY_OS_APPLE 0
#endif

#if defined(__FreeBSD__)
#define HWY_OS_FREEBSD 1
#else
#define HWY_OS_FREEBSD 0
#endif

#if defined(__OpenBSD__)
#define HWY_OS_OPENBSD 1
#else
#define HWY_OS_OPENBSD 0
#endif

// It is an error to detect multiple OSes at the same time, but OK to
// detect none of the above.
#if (HWY_OS_WIN + HWY_OS_LINUX + HWY_OS_APPLE + HWY_OS_FREEBSD +             \
     HWY_OS_OPENBSD) > 1
#error "Must not detect more than one OS"
#endif

//------------------------------------------------------------------------------
// Endianness

#if HWY_COMPILER_MSVC
#if HWY_ARCH_PPC && defined(_XBOX_VER) && _XBOX_VER >= 200
// XBox 360 is big-endian
#define HWY_IS_LITTLE_ENDIAN 0
#define HWY_IS_BIG_ENDIAN 1
#else
// All other targets supported by MSVC are little-endian
#define HWY_IS_LITTLE_ENDIAN 1
#define HWY_IS_BIG_ENDIAN 0
#endif  // HWY_ARCH_PPC && defined(_XBOX_VER) && _XBOX_VER >= 200
#elif defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
    __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define HWY_IS_LITTLE_ENDIAN 1
#define HWY_IS_BIG_ENDIAN 0
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
    __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define HWY_IS_LITTLE_ENDIAN 0
#define HWY_IS_BIG_ENDIAN 1
#else
#error "Unable to detect endianness or unsupported byte order"
#endif

#if (HWY_IS_LITTLE_ENDIAN + HWY_IS_BIG_ENDIAN) != 1
#error "Must only detect one byte order"
#endif

//------------------------------------------------------------------------------
// Features checked in set_macros-inl.h

// Compiler supports ACLE __bf16, not necessarily with operators.
//
// Disable the __bf16 type on AArch64 with GCC 13 or earlier as there is a bug
// in GCC 13 and earlier that sometimes causes BF16 constant values to be
// incorrectly loaded on AArch64, and this GCC bug on AArch64 is
// described at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111867.
#if HWY_ARCH_ARM_A64 && \
    (HWY_COMPILER_CLANG >= 1700 || HWY_COMPILER_GCC_ACTUAL >= 1400)
#define HWY_ARM_HAVE_SCALAR_BF16_TYPE 1
#else
#define HWY_ARM_HAVE_SCALAR_BF16_TYPE 0
#endif

#endif  // HIGHWAY_HWY_DETECT_COMPILER_ARCH_H_
