#
#  Copyright (c) 2018-2026, Intel Corporation
#
#  SPDX-License-Identifier: BSD-3-Clause

#
# ispc CMakeLists.txt
#
cmake_minimum_required(VERSION 3.15)

set(CMAKE_POSITION_INDEPENDENT_CODE ON)


# Set C++ standard to C++17.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)


# Set C standard to C11
set(CMAKE_C_STANDARD   11)
set(CMAKE_C_STANDARD_REQUIRED   ON)
set(CMAKE_C_EXTENSIONS OFF)


if (UNIX)
    if (NOT CMAKE_C_COMPILER)
        set(CMAKE_C_COMPILER "clang")
    endif()
    if (NOT CMAKE_CXX_COMPILER)
        set(CMAKE_CXX_COMPILER "clang++")
    endif()
endif()

# Generate compile_commands.json by default - this enables VSCode to do better job interpretting
# C++ files form the project.
set(CMAKE_EXPORT_COMPILE_COMMANDS "ON" CACHE BOOL "Export compile commands")

if (APPLE)
    # Setting ISPC_MACOS_UNIVERSAL_BINARIES to ON is conventional way of building ISPC as Universal Binary.
    # If cross compilation is needed (arm64->x86_64 or x86_64->arm64), specify CMAKE_OSX_ARCHITECTURES
    # on the command line explicitly
    option(ISPC_MACOS_UNIVERSAL_BINARIES "Build Universal Binaries on macOS" OFF)
    if (ISPC_MACOS_UNIVERSAL_BINARIES)
        message(STATUS "Building Universal Binaries (x86_64+arm64)")
        set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "OSX architectures")
    endif()

    # We need to target different minimum macOS versions on x86 and arm by default, but before
    # we execute project() command, the regular CMAKE_* variables with host info are not initialized,
    # so we do this hack to detect the platform. It's not perfect, but good enough.
    execute_process(COMMAND uname -m OUTPUT_VARIABLE UNAME)
    if (UNAME MATCHES "x86_64" OR CMAKE_OSX_ARCHITECTURES MATCHES "x86_64")
        # Note: it's ok to have 10.13 for Universal Binaries.
        set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum macOS version to support")
    else()
        set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum macOS version to support")
    endif()
    message(STATUS "Targeting minimum macOS version: ${CMAKE_OSX_DEPLOYMENT_TARGET}")

endif()

set(PROJECT_NAME ispc)
set(ISPC_BUILD TRUE)
project(${PROJECT_NAME})

## Check PIE support
include(CheckPIESupported)
check_pie_supported(OUTPUT_VARIABLE CHECK_PIE_OUTPUT LANGUAGES CXX)
if(NOT CMAKE_CXX_LINK_PIE_SUPPORTED)
    message(WARNING "PIE is not supported at link time: ${CHECK_PIE_OUTPUT}.\n"
                    "PIE link options will not be passed to linker.")
endif()

# Use solution folders.
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

set(OUTPUT_DEBUG Debug/bin)
set(OUTPUT_RELEASE Release/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin )
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib )

if(CMAKE_BUILD_TYPE)
    # Validate build type
    set(CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo")

    string(FIND "${CONFIGURATION_TYPES}" "${CMAKE_BUILD_TYPE}" MATCHED_CONFIG)
    if (${MATCHED_CONFIG} EQUAL -1)
         message(FATAL_ERROR "CMAKE_BUILD_TYPE (${CMAKE_BUILD_TYPE}) allows only the following values: ${CONFIGURATION_TYPES}")
    endif()
else(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Release")
    message(STATUS "Build type not specified: Use Release by default.")
endif(CMAKE_BUILD_TYPE)


option(ISPC_INCLUDE_EXAMPLES "Generate build targets for the ISPC examples" ON)
option(ISPC_INCLUDE_DPCPP_EXAMPLES "Generate build targets for the ISPC/DPCPP interoperability examples" OFF)
option(ISPC_INCLUDE_TESTS "Generate build targets for the ISPC tests." ON)
option(ISPC_INCLUDE_BENCHMARKS "Generate build targets for the ISPC tests." OFF)
option(ISPC_INCLUDE_RT "Generate build targets for ISPC runtime." ON)
option(ISPC_INCLUDE_UTILS "Generate build targets for the utils." ON)
option(ISPC_INCLUDE_UTILS_ONLY "Generate build targets only for the utils." OFF)
option(ISPC_LIBRARY "Build ISPC as a library" ON)
option(ISPC_LIBRARY_JIT "Build ISPC as a library with JIT capabilities" ON)
option(ISPC_PREPARE_PACKAGE "Generate build targets for ispc package" OFF)
option(ISPC_PACKAGE_EXAMPLES "Pack examples into the ISPC package" ON)
option(ISPC_SLIM_BINARY "Build ISPC as slim binary" OFF)

# Ensure ISPC_LIBRARY is OFF when ISPC_SLIM_BINARY is ON
if (ISPC_SLIM_BINARY)
    if (ISPC_LIBRARY)
        message(WARNING "ISPC_LIBRARY is being forced to OFF because ISPC_SLIM_BINARY is ON")
    endif()
    if (ISPC_LIBRARY_JIT)
        message(WARNING "ISPC_LIBRARY_JIT is being forced to OFF because ISPC_SLIM_BINARY is ON")
    endif()
    set(ISPC_LIBRARY OFF CACHE BOOL "Build ISPC as a library" FORCE)
    set(ISPC_LIBRARY_JIT OFF CACHE BOOL "Build ISPC as a library with JIT capabilities" FORCE)
endif()

# Build target for utility checking host ISA
# This target should be defined before the inclusion of FindLLVM.cmake because
# we want to build check_isa without LLVM installed.
if (ISPC_INCLUDE_UTILS)
    add_executable(check_isa "")
    target_sources(check_isa PRIVATE tools/check_isa.cpp src/isa.h)
    target_include_directories(check_isa PRIVATE src)
    set_target_properties(check_isa PROPERTIES FOLDER "Utils")
    if (NOT ISPC_PREPARE_PACKAGE)
        install (TARGETS check_isa DESTINATION bin)
    endif()
    if (ISPC_INCLUDE_UTILS_ONLY)
        # Skip the below part of CMake. It is useful when only check_isa compiling is needed.
        return()
    endif()
endif()

set(X86_HOST FALSE)
if (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "amd64|AMD64|86")
    set(X86_HOST TRUE)
endif()

include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindLLVM.cmake)
run_llvm_config(LLVM_TARGETS_BUILT "--targets-built")

set(LLVM_SUPPORTS_ARM ON)
if (NOT LLVM_TARGETS_BUILT MATCHES "AArch64" OR NOT LLVM_TARGETS_BUILT MATCHES "ARM")
    set(LLVM_SUPPORTS_ARM OFF)
    message(WARNING "LLVM does not support Aarch64 and ARM targets. Disabling these targets.")
endif()

set(LLVM_SUPPORTS_RISCV OFF)
if (LLVM_TARGETS_BUILT MATCHES "RISCV")
    set(LLVM_SUPPORTS_RISCV ON)
endif()

option(X86_ENABLED "Enable x86 support" ${X86_HOST})
option(ARM_ENABLED "Enable ARM support" ${LLVM_SUPPORTS_ARM})
# Turn off RISC-V support by default. Later we can use ${LLVM_SUPPORTS_RISCV} for default option value.
option(RISCV_ENABLED "Enable RISC-V support" OFF)
option(WASM_ENABLED "Enable experimental Web Assembly support" OFF)
option(XE_ENABLED "Enable Intel Xe support" OFF)

option(ISPC_CROSS "Build ISPC with cross compilation support" OFF)
# Default settings for cross compilation
if (ISPC_CROSS)
    option(ISPC_WINDOWS_TARGET "Build ISPC with windows target support" ON)
    option(ISPC_LINUX_TARGET "Build ISPC with linux target support" ON)
    option(ISPC_FREEBSD_TARGET "Build ISPC with freebsd target support" ON)
    # It's possbile to build macOS target on Windows or Linux, but we don't do that by default.
    option(ISPC_MACOS_TARGET "Build ISPC with macos target support" OFF)
    option(ISPC_IOS_TARGET "Build ISPC with ios target support" ON)
    option(ISPC_ANDROID_TARGET "Build ISPC with android target support" ON)
    option(ISPC_PS_TARGET "Build ISPC with ps4 and ps5 targets support" ON)
    # We silently disable some of cross targets. We should probably warn user or issue an error,
    # but it seems good enough.
    if (WIN32)
        set(ISPC_IOS_TARGET OFF)
        if ((ISPC_LINUX_TARGET OR ISPC_ANDROID_TARGET OR ISPC_PS_TARGET) AND NOT ISPC_GNUWIN32_PATH)
            message (FATAL_ERROR "Set ISPC_GNUWIN32_PATH variable for cross compilation to Linux/Android/PS4 e.g. C:/gnuwin32")
        endif()
        if (ISPC_MACOS_TARGET AND NOT ISPC_MACOS_SDK_PATH)
            message (FATAL_ERROR "Set ISPC_MACOS_SDK_PATH variable for cross compilation to MacOS e.g. C:/iusers/MacOSX10.14.sdk")
        endif()
    elseif (APPLE)
        set(ISPC_MACOS_TARGET ON)
        # If the Windows SDK and CRT are available, enable Windows
        if (NOT ISPC_WINDOWS_SDK_PATH OR NOT ISPC_WINDOWS_VCTOOLS_PATH)
            set(ISPC_WINDOWS_TARGET OFF)
        endif()
        set(ISPC_PS_TARGET OFF)
        set(ISPC_FREEBSD_TARGET OFF)
        if ((ISPC_LINUX_TARGET OR ISPC_ANDROID_TARGET) AND NOT ISPC_ANDROID_NDK_PATH)
            message (FATAL_ERROR "Set ISPC_ANDROID_NDK_PATH variable for cross compilation to Linux/Android e.g. /Users/Shared/android-ndk-r20")
        endif()
        if (ISPC_IOS_TARGET AND NOT ISPC_IOS_SDK_PATH)
            # Use standard iOS SDK location if this is not specified.
            set(command "xcrun" "--show-sdk-path" "--sdk" "iphoneos")
            execute_process(COMMAND ${command}
                OUTPUT_VARIABLE ISPC_IOS_SDK_PATH
                OUTPUT_STRIP_TRAILING_WHITESPACE
            )
            message(STATUS "Using iOS SDK path ${ISPC_IOS_SDK_PATH}")
        endif()
    else()
        # If the Windows SDK and CRT are available, enable Windows
        if (NOT ISPC_WINDOWS_SDK_PATH OR NOT ISPC_WINDOWS_VCTOOLS_PATH)
            set(ISPC_WINDOWS_TARGET OFF)
        endif()
        set(ISPC_PS_TARGET OFF)
        set(ISPC_IOS_TARGET OFF)
        if (ISPC_MACOS_TARGET AND NOT ISPC_MACOS_SDK_PATH)
            message (FATAL_ERROR "Set ISPC_MACOS_SDK_PATH variable for cross compilation to MacOS e.g. /iusers/MacOSX10.14.sdk")
        endif()
    endif()
else()
    if (WIN32)
        set(ISPC_WINDOWS_TARGET ON)
    elseif (APPLE)
        set(ISPC_MACOS_TARGET ON)
    elseif (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
        set(ISPC_FREEBSD_TARGET ON)
    else()
        set(ISPC_LINUX_TARGET ON)
    endif()
endif()
if (ISPC_LINUX_TARGET OR ISPC_ANDROID_TARGET OR ISPC_PS_TARGET
    OR ISPC_FREEBSD_TARGET OR ISPC_IOS_TARGET OR ISPC_MACOS_TARGET)
    set(ISPC_UNIX_TARGET ON)
else()
    set(ISPC_UNIX_TARGET OFF)
endif()

if (APPLE)
    # Use standard macOS SDK location if this is not specified.
    if (NOT ISPC_MACOS_SDK_PATH)
        set(command "xcrun" "--show-sdk-path")
        execute_process(COMMAND ${command}
            OUTPUT_VARIABLE ISPC_MACOS_SDK_PATH
            OUTPUT_STRIP_TRAILING_WHITESPACE
        )
        message(STATUS "Using macOS SDK path ${ISPC_MACOS_SDK_PATH}")
    endif()

    # Workaround CMake/CPack issue https://gitlab.kitware.com/cmake/cmake/-/issues/24601
    # When llvm toolchain is presented in PATH, llvm-strip is preferred over
    # macOS strip. However, CPack fails to correctly call llvm-strip because it
    # adds flags like `-u` that are macOS strip specific.
    # It may be removed later when CMake fixes ambiguity.
    if (${CMAKE_VERSION} VERSION_LESS "3.27.0")
        set(CMAKE_STRIP strip)
    endif()
endif()

set(ISPC_MACOS_ARM_TARGET OFF)
if (ISPC_MACOS_TARGET AND ISPC_MACOS_SDK_PATH)
    # Get macOS SDK version.
    # xcrun is known not to work for standalone SDK or in the default environment
    # on some systems (without explicitly running "xcode-select -s <path>" first).
    # So we try to get the version from the SDK path first, and then fall back
    # to looking at the SDK folder name.
    set(command "xcrun" "--sdk" ${ISPC_MACOS_SDK_PATH} "--show-sdk-version")
    execute_process(COMMAND ${command}
        OUTPUT_VARIABLE SDK_VER
        RESULT_VARIABLE XCRUN_RESULT
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    if (NOT XCRUN_RESULT STREQUAL "0")
        if (IS_SYMLINK ${ISPC_MACOS_SDK_PATH})
            # Follow symlinks, relative and absolute.
            file(READ_SYMLINK "${ISPC_MACOS_SDK_PATH}" ISPC_MACOS_SDK_PATH_NEW)
            if (NOT IS_ABSOLUTE "${ISPC_MACOS_SDK_PATH_NEW}")
                get_filename_component(ISPC_MACOS_SDK_PATH_DIR "${ISPC_MACOS_SDK_PATH}" DIRECTORY)
                set(ISPC_MACOS_SDK_PATH_NEW "${ISPC_MACOS_SDK_PATH_DIR}/${ISPC_MACOS_SDK_PATH_NEW}")
            endif()
            set(ISPC_MACOS_SDK_PATH "${ISPC_MACOS_SDK_PATH_NEW}")
        endif()

        string(REGEX MATCH "MacOSX([0-9]*.[0-9]*).sdk" _ ${ISPC_MACOS_SDK_PATH})
        set(SDK_VER "${CMAKE_MATCH_1}")
    endif()

    message(STATUS "MacOS_SDK version: ${SDK_VER}")

    if ("${SDK_VER}" STREQUAL "")
        message(WARNING "MacOS SDK version was not detected, assuming 11.0 or later, enabling ARM support")
        set(ISPC_MACOS_ARM_TARGET ON)
    elseif("${SDK_VER}" VERSION_GREATER_EQUAL "11.0")
        message(STATUS "MacOS_SDK supports ARM (SDK ver >= 11.0)")
        set(ISPC_MACOS_ARM_TARGET ON)
    else()
        message(STATUS "MacOS_SDK does NOT supports ARM (SDK ver < 11.0)")
    endif()
endif()

# If Xe target is enabled, generate build targets for Xe examples and ISPCRT on Linux
if (XE_ENABLED)
    option(ISPC_INCLUDE_XE_EXAMPLES "Generate build targets for the ISPC Xe examples" ON)
endif()

if (UNIX)
    option(ISPC_STATIC_STDCXX_LINK "Link statically with libstdc++ and libgcc" OFF)
    if (ISPC_PREPARE_PACKAGE AND (NOT APPLE))
        option(ISPC_STATIC_LINK "Link statically (all except glibc)" ON)
    else()
        option(ISPC_STATIC_LINK "Link statically (all except glibc)" OFF)
    endif()
    option(ISPC_USE_ASAN "Build ispc with address sanitizer instrumentation using clang compiler" OFF)
endif()

set(BITCODE2CPP "${CMAKE_CURRENT_SOURCE_DIR}/scripts/bitcode2cpp.py")

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/ispcrt/cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FixWindowsPath.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Git.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CommonStdlibBuiltins.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateBuiltins.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenericTargets.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Stdlib.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/StdlibFamilies.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Version.cmake)

# Get ispc version
get_ispc_version("${CMAKE_CURRENT_SOURCE_DIR}/common/version.h")

# Construct full version string
set(ISPC_VERSION "${ISPC_VERSION_MAJOR}.${ISPC_VERSION_MINOR}.${ISPC_VERSION_PATCH}${ISPC_VERSION_SUFFIX}")

find_package(Python3 REQUIRED)
    if (NOT Python3_Interpreter_FOUND)
        message(FATAL_ERROR "Python interpreter is not found")
    endif()

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

find_package(BISON 3.0 REQUIRED)
    if (BISON_FOUND)
        set(BISON_INPUT src/parse.yy)
        set(BISON_CPP_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/parse.cc)
        set(BISON_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/parse.hh
                         ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/parse.output)
        add_custom_command (
            OUTPUT ${BISON_CPP_OUTPUT} ${BISON_OUTPUT}
            COMMAND ${BISON_EXECUTABLE} -d -t -v
                --output=${BISON_CPP_OUTPUT}
                ${BISON_INPUT}
            MAIN_DEPENDENCY ${BISON_INPUT}
            COMMENT "Generating parse.cc"
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    )
        # Create explicit target to ensure parser generation completes before compilation
        add_custom_target(generate-parser DEPENDS ${BISON_CPP_OUTPUT} ${BISON_OUTPUT})
    endif()

find_package(FLEX 2.6 REQUIRED)
    if (FLEX_FOUND)
        set(FLEX_INPUT  src/lex.ll)
        set(FLEX_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lex.cpp)
        add_custom_command(
            OUTPUT ${FLEX_OUTPUT}
            COMMAND ${FLEX_EXECUTABLE}
                --outfile=${FLEX_OUTPUT}
                ${FLEX_INPUT}
            MAIN_DEPENDENCY ${FLEX_INPUT}
            COMMENT "Generating lex.cpp"
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        )
    endif()

if (NOT X86_ENABLED AND NOT ARM_ENABLED AND NOT RISCV_ENABLED AND NOT WASM_ENABLED AND NOT XE_ENABLED)
  message( FATAL_ERROR "Either X86, ARM, RISC-V, WASM or XE targets need to be enabled.")
endif ()

# All possible ISPC targets
list(APPEND X86_TARGETS
    sse2-i32x4 sse2-i32x8
    sse4-i32x4 sse4-i32x8 sse4-i8x16 sse4-i16x8
    # Note here that avx1-i32x4 is aliased to sse4-i32x4 plus avx attribute,
    # sse41* targets are aliased to corresponding sse4 targets.
    avx1-i32x8 avx1-i32x16 avx1-i64x4
    avx2-i8x32 avx2-i16x16
    avx2-i32x4 avx2-i32x8 avx2-i32x16 avx2-i64x4
    avx2vnni-i32x4 avx2vnni-i32x8 avx2vnni-i32x16
    avx512skx-x4 avx512skx-x8 avx512skx-x16 avx512skx-x16-nozmm
    avx512skx-x32 avx512skx-x64
    avx512icl-x4 avx512icl-x8 avx512icl-x16 avx512icl-x16-nozmm
    avx512icl-x32 avx512icl-x64
    avx512spr-x4 avx512spr-x8 avx512spr-x16 avx512spr-x32 avx512spr-x64
    avx512gnr-x4 avx512gnr-x8 avx512gnr-x16 avx512gnr-x32 avx512gnr-x64)

if (${LLVM_VERSION_NUMBER} VERSION_GREATER_EQUAL "20.1.2")
    list(APPEND X86_TARGETS avx10_2dmr-x4 avx10_2dmr-x8 avx10_2dmr-x16 avx10_2dmr-x32 avx10_2dmr-x64)
endif()

list(APPEND ARM_TARGETS neon-i8x16 neon-i16x8 neon-i32x4 neon-i32x8 neon-i8x32 neon-i16x16)
list(APPEND RISCV_TARGETS rvv-x4)
list(APPEND WASM_TARGETS wasm-i32x4)
list(APPEND XE_TARGETS xelp-x16 xelp-x8 xehpg-x16 xehpg-x8 xehpc-x16 xehpc-x32 xelpg-x16 xelpg-x8
                       xe2hpg-x16 xe2hpg-x32 xe2lpg-x16 xe2lpg-x32)

if (X86_ENABLED)
    list(APPEND ISPC_TARGETS ${X86_TARGETS})
endif()
if (ARM_ENABLED)
    list(APPEND ISPC_TARGETS ${ARM_TARGETS})
endif()
if (RISCV_ENABLED)
    list(APPEND ISPC_TARGETS ${RISCV_TARGETS})
endif()
if (WASM_ENABLED)
    list(APPEND ISPC_TARGETS ${WASM_TARGETS})
endif()
if (XE_ENABLED)
    list(APPEND ISPC_TARGETS ${XE_TARGETS})
endif()

if (WASM_ENABLED)
    find_program(EMCC_EXECUTABLE emcc)
    if (NOT EMCC_EXECUTABLE)
        message(FATAL_ERROR "emcc not found!")
    endif()
    message(STATUS "EMCC_EXECUTABLE: ${EMCC_EXECUTABLE}")
endif()

if (${LLVM_VERSION_NUMBER} VERSION_LESS "18.0.0")
    message(FATAL_ERROR "ISPC requires LLVM 18.0.0 or later")
endif()

set(CLANG_LIBRARY_LIST clangFrontend clangBasic clangEdit clangLex clangSupport clangASTMatchers)

set(LLVM_COMPONENTS engine ipo bitreader bitwriter instrumentation linker option frontendopenmp passes)
if (ISPC_LIBRARY_JIT)
    list(APPEND LLVM_COMPONENTS orcjit)
endif()

# windowsdriver is a small library introduced in LLVM 15. While it's targeted at Windows only,
# it's used in the LLVM code without any ifdef and is needed on all platforms.
# An interesting fact, that on Linux it is not required, because linker is smart enough to optimize out
# the calls to unresolved symbols. It's not the case on macOS though.
list(APPEND LLVM_COMPONENTS windowsdriver)

if (X86_ENABLED)
    list(APPEND LLVM_COMPONENTS x86)
endif()
if (ARM_ENABLED)
    list(APPEND LLVM_COMPONENTS arm aarch64)
endif()
if (RISCV_ENABLED)
    list(APPEND LLVM_COMPONENTS riscv)
endif()
if (WASM_ENABLED)
    list(APPEND LLVM_COMPONENTS webassembly)
endif()
if (XE_ENABLED)
    if(CMAKE_SIZEOF_VOID_P EQUAL 4)
        set(TARGET_MODIFIER "32")
        set(PB_PATH_MODIFIER "x86")
    else()
        set(TARGET_MODIFIER "64")
        set(PB_PATH_MODIFIER "x64")
    endif()

    list(APPEND XE_LIBRARY_LIST LLVMGenXIntrinsics LLVMSPIRVLib)
endif()

get_llvm_libfiles(LLVM_LIBRARY_LIST ${LLVM_COMPONENTS})
get_llvm_cppflags(LLVM_CPP_FLAGS)

if(CMAKE_GENERATOR MATCHES "Visual Studio")
    # Visual Studio generator places binaries under bin/$(Configuration) subdirectory.
    set(BITCODE_FOLDER ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/share/ispc)
    set(INCLUDE_FOLDER ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/include)
else()
    set(BITCODE_FOLDER ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../share/ispc)
    set(INCLUDE_FOLDER ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../include)
endif()
message(STATUS "BITCODE_FOLDER is ${BITCODE_FOLDER}")
file(MAKE_DIRECTORY ${BITCODE_FOLDER})

# Generate stdlib width families from X86_TARGETS and ARM_TARGETS
# and C++ code for stdlib target mapping
define_stdlib_families()
# Generate C++ code for stdlib target mapping (used by src/target_registry.cpp)
generate_stdlib_target_map_cpp()
# Defines builtin target
generate_builtins()

# Common sources between ispc-opt and ispc
list(APPEND COMMON_SOURCES
    common/version.rc
    common/version.h
    src/bitcode_lib.cpp
    src/bitcode_lib.h
    src/builtins-decl.cpp
    src/builtins-decl.h
    src/isa.h
    src/ispc.cpp
    src/ispc.h
    src/ispc_version.h
    src/llvmutil.cpp
    src/llvmutil.h
    src/target_enums.cpp
    src/target_enums.h
    src/target_registry.cpp
    src/target_registry.h
    src/util.cpp
    src/util.h
)

# Source files used only in ispc
list(APPEND FRONTEND_SOURCES
    ${STDLIB_FILES}
    ${BISON_CPP_OUTPUT}
    ${FLEX_OUTPUT}
    ${CMAKE_CURRENT_SOURCE_DIR}/stdlib/stdlib.ispc
    src/args.cpp
    src/args.h
    src/ast.cpp
    src/ast.h
    src/builtins.cpp
    src/builtins.h
    src/ctx.cpp
    src/ctx.h
    src/decl.cpp
    src/decl.h
    src/header.cpp
    src/expr.cpp
    src/expr.h
    src/func.cpp
    src/func.h
    src/include/ispc/ispc.h
    src/intrinsics.cpp
    src/ispc_impl.cpp
    src/module.cpp
    src/module.h
    src/preprocessor.cpp
    src/stmt.cpp
    src/stmt.h
    src/sym.cpp
    src/sym.h
    src/type.cpp
    src/type.h
    src/parse.yy
    src/lex.ll
)

# ISPC optimization passes shared between ispc and ispc-opt
list(APPEND OPT_SOURCES
    src/opt.cpp
    src/opt.h
    src/opt/CheckIRForXeTarget.cpp
    src/opt/CheckIRForXeTarget.h
    src/opt/GatherCoalescePass.cpp
    src/opt/GatherCoalescePass.h
    src/opt/IsCompileTimeConstant.cpp
    src/opt/IsCompileTimeConstant.h
    src/opt/ImproveMemoryOps.cpp
    src/opt/ImproveMemoryOps.h
    src/opt/InstructionSimplify.cpp
    src/opt/InstructionSimplify.h
    src/opt/IntrinsicsOptPass.cpp
    src/opt/IntrinsicsOptPass.h
    src/opt/LowerISPCIntrinsics.cpp
    src/opt/LowerISPCIntrinsics.h
    src/opt/LowerAMXBuiltinsPass.cpp
    src/opt/LowerAMXBuiltinsPass.h
    src/opt/MangleOpenCLBuiltins.cpp
    src/opt/MangleOpenCLBuiltins.h
    src/opt/PeepholePass.cpp
    src/opt/PeepholePass.h
    src/opt/RemovePersistentFuncs.cpp
    src/opt/RemovePersistentFuncs.h
    src/opt/ReplaceMaskedMemOps.cpp
    src/opt/ReplaceMaskedMemOps.h
    src/opt/ReplacePseudoMemoryOps.cpp
    src/opt/ReplacePseudoMemoryOps.h
    src/opt/ReplaceStdlibShiftPass.cpp
    src/opt/ReplaceStdlibShiftPass.h
    src/opt/ScalarizePass.cpp
    src/opt/ScalarizePass.h
    src/opt/XeGatherCoalescePass.cpp
    src/opt/XeGatherCoalescePass.h
    src/opt/XeReplaceLLVMIntrinsics.cpp
    src/opt/XeReplaceLLVMIntrinsics.h
)

set(STDLIB_HEADERS amx.isph core.isph short_vec.isph stdlib.isph)
set(ALL_STDLIB_HEADERS
  amx.isph
  builtins.isph
  core.isph
  short_vec.isph
  stdlib.isph
  svml.isph
  target.isph
)
set(INTRINSICS_HEADERS
  intrinsics/emmintrin.isph
  intrinsics/xmmintrin.isph
)
set(STDLIB_ISPC_FILES stdlib/stdlib.ispc)
foreach (header ${ALL_STDLIB_HEADERS})
    list(APPEND STDLIB_ISPC_FILES stdlib/include/${header})
endforeach()

# To show stdlib.ispc in VS solution:
if (WIN32)
    set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/stdlib/stdlib.ispc" PROPERTIES HEADER_FILE_ONLY TRUE)
    source_group("ISPC" FILES "${CMAKE_CURRENT_SOURCE_DIR}/stdlib/stdlib.ispc")
endif()

# Build definitions
string(TIMESTAMP BUILD_DATE "%Y%m%d" UTC)
list(APPEND COMPILE_DEFINITIONS
    BUILD_DATE=\"${BUILD_DATE}\"
    BUILD_VERSION=\"${GIT_COMMIT_HASH}\"
)
if (UNIX)
    if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
        # Compile-time protection against static sized buffer overflows.
        list(APPEND COMPILE_DEFINITIONS _FORTIFY_SOURCE=2)
    endif()
else()
    list(APPEND COMPILE_DEFINITIONS NOMINMAX)
endif()

# Pass NDEBUG for non-debug builds on all platforms
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
    list(APPEND COMPILE_DEFINITIONS NDEBUG)
endif()

# TODO: It may be confusing that some CMake variables start with ISPC but some
# not. It can be also useful to have same name for CMake var and the
# corresponding macro definition. It is not clear why it make sense to have
# different ones.
if (X86_ENABLED)
    list(APPEND COMPILE_DEFINITIONS ISPC_X86_ENABLED)
endif()

if (ARM_ENABLED)
    list(APPEND COMPILE_DEFINITIONS ISPC_ARM_ENABLED)
endif()

if (RISCV_ENABLED)
    list(APPEND COMPILE_DEFINITIONS ISPC_RISCV_ENABLED)
endif()

if (XE_ENABLED)
    list(APPEND COMPILE_DEFINITIONS ISPC_XE_ENABLED)
endif()

if (WASM_ENABLED)
    list(APPEND COMPILE_DEFINITIONS ISPC_WASM_ENABLED)
endif()

# Compile definitions for cross compilation
if (ISPC_WINDOWS_TARGET)
    list(APPEND COMPILE_DEFINITIONS ISPC_WINDOWS_TARGET_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_WINDOWS_TARGET_OFF)
endif()
if (ISPC_LINUX_TARGET)
    list(APPEND COMPILE_DEFINITIONS ISPC_LINUX_TARGET_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_LINUX_TARGET_OFF)
endif()
if (ISPC_FREEBSD_TARGET)
    list(APPEND COMPILE_DEFINITIONS ISPC_FREEBSD_TARGET_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_FREEBSD_TARGET_OFF)
endif()
if (ISPC_MACOS_TARGET)
    list(APPEND COMPILE_DEFINITIONS ISPC_MACOS_TARGET_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_MACOS_TARGET_OFF)
endif()
if (ISPC_IOS_TARGET)
    list(APPEND COMPILE_DEFINITIONS ISPC_IOS_TARGET_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_IOS_TARGET_OFF)
endif()
if (ISPC_ANDROID_TARGET)
    list(APPEND COMPILE_DEFINITIONS ISPC_ANDROID_TARGET_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_ANDROID_TARGET_OFF)
endif()
if (ISPC_PS_TARGET)
    list(APPEND COMPILE_DEFINITIONS ISPC_PS_TARGET_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_PS_TARGET_OFF)
endif()
if (ISPC_UNIX_TARGET)
    list(APPEND COMPILE_DEFINITIONS ISPC_UNIX_TARGET_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_UNIX_TARGET_OFF)
endif()
if (ISPC_LIBRARY_JIT)
    list(APPEND COMPILE_DEFINITIONS ISPC_JIT_ON)
else()
    list(APPEND COMPILE_DEFINITIONS ISPC_JIT_OFF)
endif()

# Include directories
list(APPEND INCLUDE_DIRECTORIES
    ${LLVM_INCLUDE_DIRS}
    ${XE_DEPS_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    ${CMAKE_CURRENT_SOURCE_DIR}/src/include
    ${CMAKE_CURRENT_SOURCE_DIR}/common
    ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}
    ${CMAKE_BINARY_DIR}
)

list(APPEND COMPILE_OPTIONS)
# Compile options
if (MSVC)
    list(APPEND COMPILE_OPTIONS
        /W3
        /wd4018
        /wd4146
        /wd4244
        /wd4267
        /wd4624
        /wd4996
    )
    # Security options
    list(APPEND COMPILE_OPTIONS
        /GS
        /EHsc
    )
    set_source_files_properties(${FLEX_OUTPUT} PROPERTIES COMPILE_FLAGS /wd4005)
    set_source_files_properties(${BISON_OUTPUT} PROPERTIES COMPILE_FLAGS /wd4005)
else()
    list(APPEND COMPILE_OPTIONS
        -Wall
        -Wextra
        -Wno-unused-parameter
        ${LLVM_CPP_FLAGS}
    )
    # Disable warnings coming from LLVM headers
    if (${LLVM_VERSION_NUMBER} VERSION_GREATER_EQUAL "21.0.0")
        # This flag is only valid for C, not C++
        list(APPEND COMPILE_OPTIONS $<$<COMPILE_LANGUAGE:C>:-Wno-duplicate-decl-specifier>)
    endif()
    # Security options for both Debug and Release builds
    list(APPEND COMPILE_OPTIONS
        -fstack-protector-strong
        -fno-delete-null-pointer-checks
        -Wformat
        -Wformat-security
        -fwrapv
    )
    # Additional security options for Release mode
    # Should be combined with link-time optimization (-Wl,--gc-sections during the linking phase)
    if (CMAKE_BUILD_TYPE STREQUAL "Release")
        list(APPEND COMPILE_OPTIONS
            -fdata-sections
            -ffunction-sections
        )
    endif()
    # Since 14.0 LLVM has switched default DWARF version from v4 to v5
    # but gdb < 10.1 can't read DWARF v5.
    # https://discourse.llvm.org/t/gdb-10-1-cant-read-clangs-dwarf-v5/6035
    # Use DWARF v4 for ISPC build to allow debugging of ispc binary with gdb
    # on Ubuntu 20.04
    if (NOT CMAKE_BUILD_TYPE STREQUAL "Release")
        list(APPEND COMPILE_OPTIONS -gdwarf-4)
    endif()
endif()


if (NOT MSVC)
    list(APPEND COMPILE_OPTIONS -fno-rtti)
    if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        list(APPEND COMPILE_OPTIONS
            -Wno-register
            -Wno-error=comment
        )
        if (ISPC_LIBRARY_JIT)
            # Disable warnings about redundant move in JIT mode triggered by LLVM code.
            list(APPEND COMPILE_OPTIONS -Wno-redundant-move)
        endif()
        # The GCC warning issues false positives when one of the allocation and
        # deallocation functions is inlined into its caller but not the other.
        list(APPEND COMPILE_OPTIONS -Wno-mismatched-new-delete)
    else()
        list(APPEND COMPILE_OPTIONS
            -Wno-c99-extensions
            -Wno-deprecated-register
        )
    endif()
    if (ISPC_USE_ASAN)
        list(APPEND COMPILE_OPTIONS -fsanitize=address)
    endif()
endif()


list(APPEND LINK_OPTIONS_PRIVATE)
list(APPEND LINK_OPTIONS_PUBLIC)
# Link options
if (WIN32)
    list(APPEND LINK_OPTIONS_PRIVATE /DYNAMICBASE)
    # Control flow guard
    list(APPEND LINK_OPTIONS_PRIVATE /GUARD:CF)
    # When resolving statically linked import dependencies of the module, limit
    # the module search path to %windows%\system32 directory only.
    list(APPEND LINK_OPTIONS_PRIVATE /DEPENDENTLOADFLAG:0x800)
    if (MSVC AND NOT CMAKE_BUILD_TYPE STREQUAL "DEBUG" )
        list(APPEND LINK_OPTIONS_PUBLIC /OPT:REF /OPT:ICF)
    endif()
elseif (APPLE)
    if(CMAKE_BUILD_TYPE MATCHES Release)
        list(APPEND LINK_OPTIONS_PUBLIC "SHELL: -Wl,-dead_strip")
    endif()
else()
    # Common security hardening options for both Debug and Release
    list(APPEND LINK_OPTIONS_PUBLIC
        "SHELL: -z noexecstack"
        "SHELL: -z relro"
        "SHELL: -z now"
    )
    # Additional options for Release mode
    if(CMAKE_BUILD_TYPE MATCHES Release)
        list(APPEND LINK_OPTIONS_PUBLIC "SHELL: -Wl,--gc-sections")
    endif()
endif()

if (ISPC_STATIC_STDCXX_LINK OR ISPC_STATIC_LINK)
    list(APPEND LINK_OPTIONS_PUBLIC
        -static-libgcc
        -static-libstdc++
    )
endif()

if (ISPC_USE_ASAN)
    list(APPEND LINK_OPTIONS_PUBLIC -fsanitize=address)
endif()


list(APPEND LINK_LIBRARIES)
if (NOT WIN32 AND NOT APPLE)
    # To resolve circular dependencies between libraries use --start-group/--end-group
    list(APPEND LINK_LIBRARIES "-Wl,--start-group")
endif()

# Link against Clang libraries
foreach(clangLib ${CLANG_LIBRARY_LIST})
    # Preferred way to build ISPC is to link it with individual clang static
    # libraries. However, when they omitted it can be link with shared library
    # libclang-cpp.so. Second approach would be utilized in some controlled
    # environments, like in package managers for particular OSes (issue 2418).
    find_library(${clangLib}Path
                 NAMES
                    "${CMAKE_STATIC_LIBRARY_PREFIX}${clangLib}${CMAKE_STATIC_LIBRARY_SUFFIX}"
                    clang-cpp
                 HINTS ${LLVM_LIBRARY_DIRS})
    if (NOT ${${clangLib}Path} IN_LIST CLANG_LIBRARY_FULL_PATH_LIST)
        list(APPEND CLANG_LIBRARY_FULL_PATH_LIST ${${clangLib}Path})
    endif()
endforeach()
list(APPEND LINK_LIBRARIES ${CLANG_LIBRARY_FULL_PATH_LIST})

# Link against LLVM libraries
list(APPEND LINK_LIBRARIES ${LLVM_LIBRARY_LIST} ${CMAKE_DL_LIBS})

if (XE_ENABLED)
    # Link against Xe libraries
    foreach(xeLib ${XE_LIBRARY_LIST})
        find_library(${xeLib}Path NAMES ${xeLib} HINTS ${XE_DEPS_DIR}/lib ${XE_DEPS_DIR}/lib/${CMAKE_LIBRARY_ARCHITECTURE})
        list(APPEND XE_LIBRARY_FULL_PATH_LIST ${${xeLib}Path})
    endforeach()
    list(APPEND LINK_LIBRARIES ${XE_LIBRARY_FULL_PATH_LIST})
endif()

if (NOT WIN32 AND NOT APPLE)
    list(APPEND LINK_LIBRARIES "-Wl,--end-group")
endif()

# System libraries, our own and transitive dependencies from LLVM libs.
if (WIN32)
    list(APPEND LINK_LIBRARIES version.lib shlwapi.lib odbc32.lib odbccp32.lib)
    # Required after LLVM commit a5ffabc
    list(APPEND LINK_LIBRARIES ws2_32.lib)
    # Required after LLVM commit cb7690a
    if (${LLVM_VERSION_NUMBER} VERSION_GREATER_EQUAL "19.1.0")
        list(APPEND LINK_LIBRARIES ntdll.lib)
    endif()
else()
    run_llvm_config(LLVM_SYSTEM_LIBS "--system-libs")
    if (LLVM_SYSTEM_LIBS MATCHES "pthread")
        # ISPC doesn't use pthreads directly, but some LLVM does. Although, it
        # looks like we do not use LLVM code that depend on pthreads, don't we?
        find_library(PTHREAD_LIB REQUIRED NAMES pthread)
        message(STATUS "Link with pthread library ${PTHREAD_LIB}")
        list(APPEND LINK_LIBRARIES ${PTHREAD_LIB})
    endif()
    if (LLVM_SYSTEM_LIBS MATCHES "tinfo" OR LLVM_SYSTEM_LIBS MATCHES "curses")
        find_library(TINFO_LIB REQUIRED NAMES terminfo tinfo curses ncurses ncursesw)
        message(STATUS "Link with tinfo library ${TINFO_LIB}")
        list(APPEND LINK_LIBRARIES ${TINFO_LIB})
    endif()
endif()

# This function configures compile definitions, options and include dirs.
# It can be used for any objects or libraries that are built in the project.
function(configure_ispc_obj TARGET)
    target_compile_definitions(${TARGET} PRIVATE ${COMPILE_DEFINITIONS})
    target_include_directories(${TARGET} PRIVATE ${INCLUDE_DIRECTORIES})
    target_compile_options(${TARGET} PRIVATE ${COMPILE_OPTIONS})

    # Set hidden visibility for inline functions. This is needed to be in sync
    # with LLVM libraries, we do include some template code from LLVM that requires
    # this options to be in sync with LLVM. Otherwise it causes link warnings.
    set_target_properties(${TARGET} PROPERTIES VISIBILITY_INLINES_HIDDEN ON)
    if (NOT MSVC)
        set_target_properties(${TARGET} PROPERTIES CXX_EXTENSIONS OFF)
    endif()
endfunction()

# This function configures executable targets.
function(configure_ispc_exe TARGET)
    configure_ispc_obj(${TARGET})
    target_link_options(${TARGET} PRIVATE ${LINK_OPTIONS_PRIVATE})
    target_link_options(${TARGET} PUBLIC ${LINK_OPTIONS_PUBLIC})
endfunction()

# This function configures library target.
function(configure_ispc_lib TARGET)
    configure_ispc_obj(${TARGET})
    target_link_options(${TARGET} PRIVATE ${LINK_OPTIONS_PRIVATE})
    target_link_options(${TARGET} PUBLIC ${LINK_OPTIONS_PUBLIC})
    set_target_properties(${TARGET} PROPERTIES
        OUTPUT_NAME "ispc"
        VERSION ${ISPC_VERSION}
        SOVERSION ${ISPC_VERSION_MAJOR}
        WINDOWS_EXPORT_ALL_SYMBOLS ON)
endfunction()

# This function creates and configures an ISPC library target (shared for now)
function(add_ispc_library TARGET_NAME LIBRARY_TYPE OUTPUT_NAME_OVERRIDE)
    # Define the common source files and object libraries
    set(ISPC_LIB_SOURCES
        src/binary_composite.cpp
        $<TARGET_OBJECTS:builtin>
        $<TARGET_OBJECTS:stdlib>
        $<TARGET_OBJECTS:optimization>
        $<TARGET_OBJECTS:common>
        $<TARGET_OBJECTS:frontend-lib>
        $<TARGET_OBJECTS:generic-target>
    )

    # Create the library target
    add_library(${TARGET_NAME} ${LIBRARY_TYPE} ${ISPC_LIB_SOURCES})

    # Configure the library
    configure_ispc_lib(${TARGET_NAME})

    # Override output name if provided
    if(OUTPUT_NAME_OVERRIDE)
        set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME_OVERRIDE}")
    endif()

    # Set interface include directories for consumers
    target_include_directories(${TARGET_NAME} INTERFACE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/include>
        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
    )

    # Link libraries and set dependencies
    target_link_libraries(${TARGET_NAME} PRIVATE ${LINK_LIBRARIES})
    add_dependencies(${TARGET_NAME} ispc-slim)
endfunction()

# This function creates and configures ispc-slim executable target under the given NAME.
function(add_ispc_slim NAME)
    # Defines stdlib target
    generate_stdlibs(${NAME})

    # Defines targets for generic builtin implementations
    generate_generic_builtins(${NAME})

    # stdlib, stdlibs-bc, stdlibs-cpp targets are created in Stdlib.cmake
    configure_ispc_obj(stdlib)

    add_executable(${NAME} src/main.cpp src/binary_slim.cpp)
    configure_ispc_exe(${NAME})
    target_link_libraries(${NAME} ${LINK_LIBRARIES} optimization common frontend)
    add_dependencies(${NAME} builtins-bc)
    add_dependencies(generic-target-bc ${NAME})
    add_dependencies(stdlibs-bc generic-stdlib-bc)
    if (${NAME} STREQUAL "ispc")
        set_target_properties(generic-target-bc PROPERTIES EXCLUDE_FROM_ALL OFF)
        set_target_properties(generic-stdlib-bc PROPERTIES EXCLUDE_FROM_ALL OFF)
        set_target_properties(stdlibs-bc PROPERTIES EXCLUDE_FROM_ALL OFF)
    endif()
endfunction()

################################################################################
# Define libraries that are built in the project. Some of them are shared across multiple executable targets.
add_library(optimization OBJECT EXCLUDE_FROM_ALL ${OPT_SOURCES})
configure_ispc_obj(optimization)

add_library(common OBJECT EXCLUDE_FROM_ALL ${COMMON_SOURCES})
configure_ispc_obj(common)

add_library(frontend OBJECT EXCLUDE_FROM_ALL ${FRONTEND_SOURCES})
configure_ispc_obj(frontend)
add_dependencies(frontend generate-parser)

if (ISPC_LIBRARY)
    add_library(frontend-lib OBJECT EXCLUDE_FROM_ALL ${FRONTEND_SOURCES})
    configure_ispc_obj(frontend-lib)
    target_compile_definitions(frontend-lib PRIVATE ISPC_IS_LIBRARY)
    add_dependencies(frontend-lib generate-parser)
endif()

# builtin target is created in GenerateBuiltins.cmake
configure_ispc_obj(builtin)

# Define executable targets
add_executable(ispc-opt tools/ispc-opt.cpp)
configure_ispc_exe(ispc-opt)
# TODO! should we link against all the same LLVM/Clang libraries? Clang libs is definetely not needed
target_link_libraries(ispc-opt ${LINK_LIBRARIES} builtin optimization common)

# The final target has to be ispc due to two use-cases we do not want to
# interfere with: make ispc and $<TARGET_FILE:ispc>.
if (ISPC_SLIM_BINARY)
    add_ispc_slim(ispc)
    message(STATUS "ISPC will be built as a slim binary")
else()
    add_ispc_slim(ispc-slim)
    configure_ispc_obj(generic-target)

    add_executable(ispc src/main.cpp src/binary_composite.cpp)
    configure_ispc_exe(ispc)
    target_link_libraries(ispc ${LINK_LIBRARIES} builtin stdlib optimization common frontend generic-target)
    add_dependencies(ispc ispc-slim)
    message(STATUS "ISPC will be built as a composite binary")
endif()

install (TARGETS ispc DESTINATION bin)
if (ISPC_SLIM_BINARY)
    foreach (header ${STDLIB_HEADERS})
        install (FILES "stdlib/include/${header}" DESTINATION include/stdlib)
    endforeach()
    install(DIRECTORY ${BITCODE_FOLDER} DESTINATION share)
else()
    # Install short_vec.isph and amx.isph for composite binary
    install (FILES "stdlib/include/short_vec.isph" DESTINATION include/stdlib)
    install (FILES "stdlib/include/amx.isph" DESTINATION include/stdlib)
endif()

################################################################################
# ISPC Library and Headers Section

# ISPC header copying setup
file(MAKE_DIRECTORY ${INCLUDE_FOLDER})
set(ISPC_HEADER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/include/ispc)
set(ISPC_HEADER_DEST ${INCLUDE_FOLDER}/ispc)

# Create a target for copying ISPC headers
add_custom_target(copy-ispc-headers ALL
    COMMAND ${CMAKE_COMMAND} -E copy_directory ${ISPC_HEADER_SRC} ${ISPC_HEADER_DEST}
    DEPENDS ${ISPC_HEADER_SRC}
    COMMENT "Copying src/include/ispc to build/include/ispc/"
    VERBATIM
)

# ISPC shared library creation and installation
set(ISPC_SHARED_LIBRARY_AVAILABLE FALSE)
if (ISPC_LIBRARY AND NOT ISPC_SLIM_BINARY)
    # Add libispc shared library target with default output name "ispc"
    add_ispc_library(libispc_shared SHARED "")

    # Install shared library with export
    install(TARGETS libispc_shared
        EXPORT ${PROJECT_NAME}_Exports
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    )

    # Install public headers
    install(DIRECTORY src/include/ispc
        DESTINATION include
        FILES_MATCHING PATTERN "*.h")
    set(ISPC_SHARED_LIBRARY_AVAILABLE TRUE)
endif()

# Configure the package config file
configure_package_config_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ispcConfig.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/cmake/ispcConfig.cmake"
INSTALL_DESTINATION
    ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)

# Configure the version file
write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/cmake/ispcConfigVersion.cmake"
    VERSION ${ISPC_VERSION}
    COMPATIBILITY SameMajorVersion
)

# Install the package configuration files
install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/cmake/ispcConfig.cmake"
    "${CMAKE_CURRENT_BINARY_DIR}/cmake/ispcConfigVersion.cmake"
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)

# Install the export set only if library targets exist
if (ISPC_LIBRARY AND NOT ISPC_SLIM_BINARY)
    add_dependencies(libispc_shared copy-ispc-headers)
    install(EXPORT ${PROJECT_NAME}_Exports
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
        NAMESPACE ${PROJECT_NAME}::
    )
endif()

# Install intrinsics headers
foreach (header ${INTRINSICS_HEADERS})
    install (FILES "stdlib/include/${header}" DESTINATION include/intrinsics)
endforeach()

set(ISPC_DEPS generic-target-bc stdlibs-bc stdlib-headers)

if (ISPC_INCLUDE_TESTS)
    add_subdirectory(tests)
endif()

if (ISPC_INCLUDE_BENCHMARKS)
    enable_testing()
    add_subdirectory(benchmarks)
endif()

if (ISPC_INCLUDE_RT)
    add_subdirectory(ispcrt)
endif()

if (NOT X86_ENABLED)
    # examples use x86 targets
    set(ISPC_INCLUDE_EXAMPLES OFF)
endif()

if (ISPC_INCLUDE_EXAMPLES AND NOT ISPC_PREPARE_PACKAGE)
    include(ExternalProject)
    ExternalProject_Add(ispc_cpu_examples
        PREFIX cpu_examples
        DEPENDS ispc ${ISPC_DEPS}
        SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/examples/cpu"
        CMAKE_CACHE_ARGS
            -DISPC_BUILD:BOOL=TRUE
            -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
            -DCMAKE_ISPC_COMPILER:PATH=$<TARGET_FILE:ispc>
            -DISPC_EXECUTABLE:PATH=$<TARGET_FILE:ispc>
            -DCMAKE_C_COMPILER:PATH=${CMAKE_C_COMPILER}
            -DCMAKE_CXX_COMPILER:PATH=${CMAKE_CXX_COMPILER}
        INSTALL_COMMAND ""
    )
endif()

if (ISPC_INCLUDE_XE_EXAMPLES AND ISPC_INCLUDE_RT AND NOT ISPC_PREPARE_PACKAGE)
    include(ExternalProject)
    if (WIN32)
        set(ISPCRT_LIB ${CMAKE_CURRENT_BINARY_DIR}/ispcrt/${CMAKE_CFG_INTDIR}/ispcrt.lib)
    else()
        set(ISPCRT_LIB $<TARGET_FILE:ispcrt>)
    endif()
    if (ISPC_INCLUDE_BENCHMARKS)
        # Duplicated ExternalProject_Add for xpu examples when
        # ISPC_INCLUDE_BENCHMARKS is on and off since all efforts of
        # using cmake generator conditional expressions were unsuccessful.
        ExternalProject_Add(ispc_xpu_examples
            PREFIX xpu_examples
            DEPENDS ispc ${ISPC_DEPS} ispcrt benchmark
            SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/examples/xpu"
            CMAKE_CACHE_ARGS
                -DISPC_BUILD:BOOL=TRUE
                -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
                -DCMAKE_ISPC_COMPILER:PATH=$<TARGET_FILE:ispc>
                -DISPC_EXECUTABLE:PATH=$<TARGET_FILE:ispc>
                -DISPCRT_LIB:PATH=${ISPCRT_LIB}
                -DLEVEL_ZERO_ROOT:PATH=${LEVEL_ZERO_ROOT}
                -DBENCHMARK_LIB:PATH=$<TARGET_FILE:benchmark>
                -DCMAKE_C_COMPILER:PATH=${CMAKE_C_COMPILER}
                -DCMAKE_CXX_COMPILER:PATH=${CMAKE_CXX_COMPILER}
                -DCMAKE_C_FLAGS:PATH=${CMAKE_C_FLAGS}
                -DCMAKE_CXX_FLAGS:PATH=${CMAKE_CXX_FLAGS}
                -DISPC_INCLUDE_BENCHMARKS:BOOL=${ISPC_INCLUDE_BENCHMARKS}
            INSTALL_COMMAND ""
        )
    else()
        ExternalProject_Add(ispc_xpu_examples
            PREFIX xpu_examples
            DEPENDS ispc ${ISPC_DEPS} ispcrt
            SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/examples/xpu"
            CMAKE_CACHE_ARGS
                -DISPC_BUILD:BOOL=TRUE
                -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
                -DCMAKE_ISPC_COMPILER:PATH=$<TARGET_FILE:ispc>
                -DISPC_EXECUTABLE:PATH=$<TARGET_FILE:ispc>
                -DISPCRT_LIB:PATH=${ISPCRT_LIB}
                -DLEVEL_ZERO_ROOT:PATH=${LEVEL_ZERO_ROOT}
                -DCMAKE_C_COMPILER:PATH=${CMAKE_C_COMPILER}
                -DCMAKE_CXX_COMPILER:PATH=${CMAKE_CXX_COMPILER}
                -DCMAKE_C_FLAGS:PATH=${CMAKE_C_FLAGS}
                -DCMAKE_CXX_FLAGS:PATH=${CMAKE_CXX_FLAGS}
            INSTALL_COMMAND ""
    )
    endif()
endif()

if (ISPC_PREPARE_PACKAGE)
    if (ISPC_PACKAGE_EXAMPLES)
        if (XE_ENABLED)
            install (DIRECTORY "${PROJECT_SOURCE_DIR}/examples/" DESTINATION examples)
        else()
            install (DIRECTORY "${PROJECT_SOURCE_DIR}/examples/" DESTINATION examples PATTERN "xpu" EXCLUDE)
        endif()
    endif()
    install (DIRECTORY "${PROJECT_SOURCE_DIR}/contrib/" DESTINATION contrib)
    install (FILES "${PROJECT_SOURCE_DIR}/LICENSE.txt" DESTINATION .)
    install (FILES "${PROJECT_SOURCE_DIR}/third-party-programs.txt" DESTINATION .)
    install (FILES "${PROJECT_SOURCE_DIR}/docs/ReleaseNotes.txt" DESTINATION .)

    # Clone corresponding version of documentation
    include(ExternalProject)
    ExternalProject_Add(ispc_web
      GIT_REPOSITORY    https://github.com/ispc/ispc.github.com.git
      GIT_TAG ${ISPC_DOC_REPO_TAG}
      PREFIX ispc_web
      SOURCE_DIR ispc_web/repo
      CONFIGURE_COMMAND cmake -E echo "Skipping configure step."
      BUILD_COMMAND cmake -E echo "Skipping build step."
      INSTALL_COMMAND cmake -E echo "Skipping install step."
    )
    ExternalProject_Get_Property(ispc_web SOURCE_DIR)
    set(ISPC_WEB_SOURCE_DIR "${SOURCE_DIR}")
    set(ISPC_DOCS  ${ISPC_WEB_SOURCE_DIR}/faq.html
                   ${ISPC_WEB_SOURCE_DIR}/ispc.html
                   ${ISPC_WEB_SOURCE_DIR}/perfguide.html)

    install(FILES ${ISPC_DOCS} DESTINATION .)
    install(DIRECTORY "${ISPC_WEB_SOURCE_DIR}/css" DESTINATION .)

    # CPack configuration
    if (WIN32)
        set(CPACK_GENERATOR "ZIP")
        set(ISPC_SYSTEM_NAME "windows")
        # WIX generator is currently turned off.
        # To turn it on uncomment code below:
        # set(CPACK_GENERATOR "WIX" "ZIP")
        # set(CPACK_WIX_UPGRADE_GUID "EDD858F2-19B0-4E5B-B9B9-2A09B85D451C")
        # set(CPACK_WIX_CMAKE_PACKAGE_REGISTRY "ISPC")
        # WIX generator expects version in format x.y.z where x, y, z are numbers
        set(CPACK_PACKAGE_VERSION "${ISPC_VERSION_MAJOR}.${ISPC_VERSION_MINOR}.${ISPC_VERSION_PATCH}")
    else()
        set(CPACK_GENERATOR "TGZ")
        if (APPLE)
            set(ISPC_SYSTEM_NAME "macOS")
        elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
            set(ISPC_SYSTEM_NAME "freebsd")
        else()
            set(ISPC_SYSTEM_NAME "linux")
        endif()
    endif()
    set(CPACK_PACKAGE_NAME "Intel(R) Implicit SPMD Program Compiler")
    set(CPACK_PACKAGE_DESCRIPTION "Intel(R) Implicit SPMD Program Compiler")
    set(CPACK_PACKAGE_VERSION_MAJOR ${ISPC_VERSION_MAJOR})
    set(CPACK_PACKAGE_VERSION_MINOR ${ISPC_VERSION_MINOR})
    set(CPACK_PACKAGE_VERSION_PATCH ${ISPC_VERSION_PATCH}${ISPC_VERSION_SUFFIX})
    set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt")
    set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")

    # Sign files in the package if ISPC_SIGN_KEY is set
    if (DEFINED ISPC_SIGN_KEY)
        configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/Signing.cmake.in"
                       "${CMAKE_CURRENT_BINARY_DIR}/Signing.cmake" @ONLY)
        set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_BINARY_DIR}/Signing.cmake")
    endif()
    # Allow use custom package name with -DISPC_PACKAGE_NAME
    if (NOT ISPC_PACKAGE_NAME)
        set(ISPC_ARCH_SUFFIX)
        if (APPLE)
            if (CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_ARCHITECTURES MATCHES "arm64")
                set(ISPC_ARCH_SUFFIX ".universal")
            elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" OR
                   (CMAKE_OSX_ARCHITECTURES STREQUAL "" AND CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64"))
                set(ISPC_ARCH_SUFFIX ".x86_64")
            elseif(CMAKE_OSX_ARCHITECTURES MATCHES "arm64" OR
                   (CMAKE_OSX_ARCHITECTURES STREQUAL "" AND CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64"))
                set(ISPC_ARCH_SUFFIX ".arm64")
            else()
                message (FATAL_ERROR "Was not able to detect package architecture")
            endif()
        elseif (UNIX)
            # Add .aarch64 suffix if the package is built on AARCH64 system.
	    # On Linux we don't build cross packages, so host corresponds to target platform.
            if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
                set(ISPC_ARCH_SUFFIX ".aarch64")
            endif()
        endif()

        if (${CPACK_PACKAGE_VERSION_PATCH} MATCHES ".*dev")
            string(CONCAT CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}"
                                "-trunk"
                                "-${ISPC_SYSTEM_NAME}"
                                "${ISPC_ARCH_SUFFIX}")
            string(CONCAT EXAMPLES_PACKAGE_FILE_NAME "ispc-examples-trunk")
        else()
            string(CONCAT CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}"
                                "-v${CPACK_PACKAGE_VERSION_MAJOR}"
                                ".${CPACK_PACKAGE_VERSION_MINOR}"
                                ".${CPACK_PACKAGE_VERSION_PATCH}"
                                "-${ISPC_SYSTEM_NAME}"
                                "${ISPC_ARCH_SUFFIX}")
            string(CONCAT EXAMPLES_PACKAGE_FILE_NAME "ispc-examples"
                                "-v${CPACK_PACKAGE_VERSION_MAJOR}"
                                ".${CPACK_PACKAGE_VERSION_MINOR}"
                                ".${CPACK_PACKAGE_VERSION_PATCH}")
        endif()
    else()
        set (CPACK_PACKAGE_FILE_NAME ${ISPC_PACKAGE_NAME})
    endif()

    if (WIN32)
        set(CPACK_PACKAGE_INSTALL_DIRECTORY "ISPC/${CPACK_PACKAGE_FILE_NAME}")
    endif()
    set(CPACK_PACKAGE_VENDOR "Intel Corporation")

    # Strip binaries
    set(CPACK_STRIP_FILES TRUE)

    include(CPack)

    # Custom target to pack only examples
    if (WIN32)
        add_custom_target(package-examples
            COMMENT "Package examples directory to ${EXAMPLES_PACKAGE_FILE_NAME}.zip"
            COMMAND ${CMAKE_COMMAND} -E tar cf "${CMAKE_BINARY_DIR}/${EXAMPLES_PACKAGE_FILE_NAME}.zip" --format=zip examples
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
            )
    else()
        add_custom_target(package-examples
            COMMENT "Package examples directory to ${EXAMPLES_PACKAGE_FILE_NAME}.tar.gz"
            COMMAND ${CMAKE_COMMAND} -E tar cfz "${CMAKE_BINARY_DIR}/${EXAMPLES_PACKAGE_FILE_NAME}.tar.gz" examples
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
            )
    endif()

endif()
