# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
#

## list of available instruction sets
set(_ARCH_LIST ANY SSE42 AVX AVX2 AVX512F)

set(_ACCEPTED_ARCHS_ANY     "^(ANY)$")
set(_ACCEPTED_ARCHS_SSE42   "^(ANY|SSE42)$")
set(_ACCEPTED_ARCHS_AVX     "^(ANY|SSE42|AVX)$")
set(_ACCEPTED_ARCHS_AVX2    "^(ANY|SSE42|AVX|AVX2)$")
set(_ACCEPTED_ARCHS_AVX512F "^(ANY|SSE42|AVX|AVX2|AVX512F)$")

## Arch specific definitions
set(_DEFINE_ANY      "")
set(_DEFINE_SSE42    "HAVE_SSE42"   ${_DEFINE_ANY})
set(_DEFINE_AVX      "HAVE_AVX"     ${_DEFINE_SSE42})
set(_DEFINE_AVX2     "HAVE_AVX2"    ${_DEFINE_AVX})
set(_DEFINE_AVX512F  "HAVE_AVX512F" ${_DEFINE_AVX2})

## Arch specific compile options
ie_avx512_optimization_flags(_FLAGS_AVX512F)
ie_avx2_optimization_flags  (_FLAGS_AVX2)
ie_sse42_optimization_flags (_FLAGS_SSE42)
set(_FLAGS_AVX "")  ## TBD is not defined for IE project yet
set(_FLAGS_ANY "")  ##

## way to duplicate file via cmake tool set
if (UNIX)
    ## Clone sources via sym link because it allow to modify original file in IDE along with debug
    set(TO_DUPLICATE create_symlink)
else()
    ## Windows and others - just copy
    set(TO_DUPLICATE copy)
endif()

set(DISPATCHER_GEN_SCRIPT         ${CMAKE_CURRENT_LIST_DIR}/cross_compiled_disp_gen.cmake)
set(DISPATCHER_GEN_OPTIONS_HOLDER ${CMAKE_CURRENT_LIST_DIR}/cross_compiled_disp_gen_options.in)


#######################################
#
#  Allow to enable multiple cross compilation of source file inside one module
#  with keeping requirements on minimal instruction set. The CPU check performed
#  in runtime via common utils declared in "ie_system_conf.h".
#
#  Usage example:
#  cross_compiled_file(<target>
#         ARCH
#            ANY       <source_file>
#            SSE SSE42 <source_file>
#            AVX AVX2  <source_file>
#            AVX512F   <source_file>
#         API <header_file>
#         NAMESPACE <namespace>   # like "IE::Ext::CPU::XARCH"
#         NAME <function_name>    # like "my_fun"
#     )
#
function(cross_compiled_file TARGET)
    set(oneValueArgs   API        ## Header with declaration of cross compiled function
                       NAMESPACE  ## The namespace where cross compiled function was declared
                       NAME)      ## String with function signature to make cross compiled
    set(multiValueArgs ARCH)      ## List of architecture described in _ARCH_LIST
    cmake_parse_arguments(X "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    ## verification
    if(X_UNPARSED_ARGUMENTS)
        message(FATAL_ERROR "Unknown argument: " ${X_UNPARSED_ARGUMENTS})
    endif()
    if((NOT TARGET) OR (NOT X_NAME) OR (NOT X_NAMESPACE) OR (NOT X_API) OR (NOT X_ARCH))
        message(FATAL_ERROR "Missed arguments")
    endif()

    _currently_requested_top_arch(TOP_ARCH)
    set(_CURRENT_ARCH_FILTER  "${_ACCEPTED_ARCHS_${TOP_ARCH}}")

    ## format: ARCH1 ARCH2 <src1> ARCH3 <src2> ...
    foreach(_it ${X_ARCH})
        if (_it IN_LIST _ARCH_LIST)
            ## that is arch ID
            set(_arch ${_it})
            if(_arch MATCHES ${_CURRENT_ARCH_FILTER})
                list(APPEND _CUR_ARCH_SET ${_arch})
                list(APPEND _FULL_ARCH_SET ${_arch})
            endif()
        else()
            ## that is source file name
            set(_src_name ${_it})
            _remove_source_from_target(${TARGET} ${_src_name})
            _clone_source_to_target(${TARGET} ${_src_name} "${_CUR_ARCH_SET}")
            set(_CUR_ARCH_SET "")
        endif()
    endforeach()

    _add_dispatcher_to_target(${TARGET} ${X_API} ${X_NAME} "${X_NAMESPACE}" "${_FULL_ARCH_SET}")
endfunction()


##########################################
#
#  Add source multiple time per each element in ARCH_SET.
#  Also provide corresponding arch specific flags and defines.
#
function(_clone_source_to_target TARGET SOURCE ARCH_SET)
    foreach(_arch ${ARCH_SET})
        set(_arch_dir cross-compiled/${_arch})

        get_filename_component(ARCH_NAME ${SOURCE} NAME)
        get_filename_component(ARCH_INCLUDE_DIR ${SOURCE} DIRECTORY)
        set(ARCH_SOURCE "${_arch_dir}/${ARCH_NAME}")

        add_custom_command(
                OUTPUT  ${ARCH_SOURCE}
                COMMAND ${CMAKE_COMMAND} -E make_directory
                        ${CMAKE_CURRENT_BINARY_DIR}/${_arch_dir}
                COMMAND ${CMAKE_COMMAND} -E ${TO_DUPLICATE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE}
                        ${CMAKE_CURRENT_BINARY_DIR}/${ARCH_SOURCE}
                DEPENDS ${SOURCE}
                VERBATIM
                )

        set_property(SOURCE ${ARCH_SOURCE} APPEND_STRING PROPERTY COMPILE_FLAGS
                " ${_FLAGS_${_arch}}")

        set_property(SOURCE ${ARCH_SOURCE} APPEND PROPERTY COMPILE_DEFINITIONS
                ${_DEFINE_${_arch}}
                "XARCH=${_arch}" ## to replace XARCH with direct ARCH name
                )

        ## To make `#include "some.hpp"` valid
        set_property(SOURCE ${ARCH_SOURCE} APPEND PROPERTY INCLUDE_DIRECTORIES
                "${CMAKE_CURRENT_SOURCE_DIR}/${ARCH_INCLUDE_DIR}")

        list(APPEND _ARCH_SOURCES ${ARCH_SOURCE})
    endforeach()

    target_sources(${TARGET} PRIVATE ${_ARCH_SOURCES})
endfunction()


##########################################
#
#  Generate dispatcher for provided function
#  for archs in ARCH_SET.
#
function(_add_dispatcher_to_target TARGET HEADER FUNC_NAME NAMESPACE ARCH_SET)
    get_filename_component(DISPATCHER_NAME ${HEADER} NAME_WE)
    get_filename_component(DISPATCHER_INCLUDE_DIR ${HEADER} DIRECTORY)
    set(DISPATCHER_SOURCE     "cross-compiled/${DISPATCHER_NAME}_disp.cpp")
    set(DISPATCHER_OPT_HOLDER "cross-compiled/${DISPATCHER_NAME}_holder.txt")

    configure_file(${DISPATCHER_GEN_OPTIONS_HOLDER} ${DISPATCHER_OPT_HOLDER})

    add_custom_command(
            OUTPUT  ${DISPATCHER_SOURCE}
            COMMAND ${CMAKE_COMMAND}
                    -D "XARCH_FUNC_NAME=${X_NAME}"
                    -D "XARCH_NAMESPACES=${NAMESPACE}"
                    -D "XARCH_API_HEADER=${CMAKE_CURRENT_SOURCE_DIR}/${HEADER}"
                    -D "XARCH_DISP_FILE=${CMAKE_CURRENT_BINARY_DIR}/${DISPATCHER_SOURCE}"
                    -D "XARCH_SET=${ARCH_SET}"
                    -P ${DISPATCHER_GEN_SCRIPT}
            DEPENDS ${HEADER}
                    ${DISPATCHER_GEN_SCRIPT}
                    ${CMAKE_CURRENT_BINARY_DIR}/${DISPATCHER_OPT_HOLDER} ## Just to make run dependency on args value
            VERBATIM
    )

    set_property(SOURCE ${DISPATCHER_SOURCE} APPEND PROPERTY INCLUDE_DIRECTORIES
            "${CMAKE_CURRENT_SOURCE_DIR}/${DISPATCHER_INCLUDE_DIR}")

    target_sources(${TARGET} PRIVATE ${DISPATCHER_SOURCE})
endfunction()

#######################################
#
#  Return currently requested ARCH id
#
function(_currently_requested_top_arch VAR)
    if(ENABLE_AVX512F)
        set(RES AVX512F)
    elseif(ENABLE_AVX2)
        set(RES AVX2)
    elseif(ENABLE_SSE42)
        set(RES SSE42)
    else()
        set(RES ANY)
    endif()
    set (${VAR} "${RES}" PARENT_SCOPE)
endfunction()

#####################################
#
#  Utils to handle with cmake target
#
function(_remove_source_from_target TARGET SOURCE_FILE)
    get_target_property(ORIGINAL_SOURCES ${TARGET} SOURCES)

    ## To match by file name only. The path is any.
    list(FILTER ORIGINAL_SOURCES EXCLUDE REGEX ".*${SOURCE_FILE}$")

    set_target_properties(${TARGET}
            PROPERTIES
            SOURCES "${ORIGINAL_SOURCES}")
endfunction()
