#===-- lib/runtime/CMakeLists.txt ------------------------------------------===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===------------------------------------------------------------------------===#

include(AddFlangRTOffload)
# function checks
find_package(Backtrace)
set(HAVE_BACKTRACE ${Backtrace_FOUND})
set(BACKTRACE_HEADER ${Backtrace_HEADER})

# Fortran sources for builtin .mod files
set(module_sources
  __fortran_builtins.f90
  __cuda_builtins.f90
  __fortran_ieee_exceptions.f90
  __fortran_type_info.f90
  flang_debug.f90
  iso_fortran_env.f90
  iso_fortran_env_impl.f90
  ieee_arithmetic.f90
  ieee_exceptions.f90
  ieee_features.f90
  iso_c_binding.f90

  __cuda_device.f90
  cooperative_groups.f90
  cuda_runtime_api.f90
  cudadevice.f90
)

# List of files that are buildable for all devices.
set(supported_sources
  ${FLANG_SOURCE_DIR}/lib/Decimal/binary-to-decimal.cpp
  ${FLANG_SOURCE_DIR}/lib/Decimal/decimal-to-binary.cpp
  ISO_Fortran_binding.cpp
  allocator-registry.cpp
  allocatable.cpp
  array-constructor.cpp
  assign.cpp
  buffer.cpp
  character.cpp
  connection.cpp
  copy.cpp
  derived-api.cpp
  derived.cpp
  descriptor-io.cpp
  descriptor.cpp
  dot-product-integer.cpp
  dot-product-real.cpp
  dot-product-complex.cpp
  edit-input.cpp
  edit-output.cpp
  environment.cpp
  exceptions.cpp
  external-unit.cpp
  extrema-maxloc.cpp
  extrema-minloc.cpp
  extrema-maxloc-dim.cpp
  extrema-minloc-dim.cpp
  extrema-value.cpp
  file.cpp
  findloc-total.cpp
  findloc-dim.cpp
  format.cpp
  inquiry.cpp
  internal-unit.cpp
  io-api.cpp
  io-api-minimal.cpp
  io-error.cpp
  io-stmt.cpp
  iostat.cpp
  matmul-transpose-integer.cpp
  matmul-transpose-real.cpp
  matmul-transpose-complex.cpp
  matmul-transpose-extypes-int16.cpp
  matmul-transpose-extypes-real10.cpp
  matmul-transpose-extypes-real16.cpp
  matmul-integer.cpp
  matmul-real.cpp
  matmul-complex.cpp
  matmul-extypes-int16.cpp
  matmul-extypes-real10.cpp
  matmul-extypes-real16.cpp
  memory.cpp
  misc-intrinsic.cpp
  namelist.cpp
  non-tbp-dio.cpp
  numeric.cpp
  pointer.cpp
  product.cpp
  pseudo-unit.cpp
  ragged.cpp
  reduction.cpp
  stat.cpp
  stl-overrides.cpp
  stop.cpp
  sum.cpp
  support.cpp
  terminator.cpp
  tools.cpp
  transformational.cpp
  type-code.cpp
  type-info.cpp
  unit.cpp
  utf.cpp
  work-queue.cpp
)

# List of source not used for GPU offloading.
set(host_sources
  iso_fortran_env_impl.cpp
  command.cpp
  complex-powi.cpp
  complex-reduction.c
  exceptions.cpp
  execute.cpp
  extensions.cpp
  main.cpp
  random.cpp
  reduce.cpp
  reduction.cpp
  stop.cpp
  temporary-stack.cpp
  time-intrinsic.cpp
  trampoline.cpp
  unit-map.cpp
  io-api-server.cpp
)

# Sources that can be compiled directly for the GPU.
set(gpu_sources
  ${FLANG_SOURCE_DIR}/lib/Decimal/binary-to-decimal.cpp
  ${FLANG_SOURCE_DIR}/lib/Decimal/decimal-to-binary.cpp
  ISO_Fortran_binding.cpp
  allocator-registry.cpp
  allocatable.cpp
  array-constructor.cpp
  assign.cpp
  buffer.cpp
  character.cpp
  connection.cpp
  copy.cpp
  derived-api.cpp
  derived.cpp
  descriptor.cpp
  dot-product-integer.cpp
  dot-product-real.cpp
  dot-product-complex.cpp
  edit-output.cpp
  extrema-maxloc.cpp
  extrema-minloc.cpp
  extrema-maxloc-dim.cpp
  extrema-minloc-dim.cpp
  extrema-value.cpp
  environment.cpp
  findloc-total.cpp
  findloc-dim.cpp
  format.cpp
  inquiry.cpp
  internal-unit.cpp
  iostat.cpp
  matmul-transpose-integer.cpp
  matmul-transpose-real.cpp
  matmul-transpose-complex.cpp
  matmul-transpose-extypes-int16.cpp
  matmul-transpose-extypes-real10.cpp
  matmul-transpose-extypes-real16.cpp
  matmul-integer.cpp
  matmul-real.cpp
  matmul-complex.cpp
  matmul-extypes-int16.cpp
  matmul-extypes-real10.cpp
  matmul-extypes-real16.cpp
  memory.cpp
  misc-intrinsic.cpp
  non-tbp-dio.cpp
  numeric.cpp
  pointer.cpp
  product.cpp
  ragged.cpp
  stat.cpp
  stop.cpp
  sum.cpp
  support.cpp
  terminator.cpp
  tools.cpp
  transformational.cpp
  type-code.cpp
  type-info.cpp
  utf.cpp
  work-queue.cpp
  complex-powi.cpp
  reduce.cpp
  reduction.cpp
  temporary-stack.cpp
  io-api-gpu.cpp
)

file(GLOB_RECURSE public_headers
  "${FLANG_RT_SOURCE_DIR}/include/flang_rt/*.h"
  "${FLANG_SOURCE_DIR}/include/flang/Common/*.h"
  )

file(GLOB_RECURSE private_headers
  "${FLANG_RT_SOURCE_DIR}/lib/flang_rt/*.h"
  "${FLANG_SOURCE_DIR}/lib/Common/*.h"
  )

if (LLVM_TARGET_TRIPLE MATCHES "^ppc|^powerpc")
  list(APPEND module_sources
    __ppc_types.f90
    __ppc_intrinsics.f90
    mma.f90
  )
endif ()

# Compile as CUDA-Fortran, not directly supported by CMake
set_property(SOURCE
    __cuda_device.f90
    cooperative_groups.f90
    cuda_runtime_api.f90
    cudadevice.f90
  APPEND PROPERTY
    COMPILE_OPTIONS --offload-host-only -xcuda
)

# Import changes from flang_rt.quadmath
set(f128_sources "")
if (TARGET FortranFloat128MathILib)
  get_target_property(f128_sources
    FortranFloat128MathILib INTERFACE_SOURCES
    )
  if (f128_sources)
    # The interface may define special macros for Float128Math files,
    # so we need to propagate them.
    get_target_property(f128_defs
      FortranFloat128MathILib INTERFACE_COMPILE_DEFINITIONS
      )
    set_property(SOURCE ${f128_sources}
      APPEND PROPERTY COMPILE_DEFINITIONS
      ${f128_defs}
      )
    get_target_property(f128_include_dirs
      FortranFloat128MathILib INTERFACE_INCLUDE_DIRECTORIES
      )
    set_property(SOURCE ${f128_sources}
      APPEND PROPERTY INCLUDE_DIRECTORIES
      ${f128_include_dirs}
      )
  else ()
    # Change `f128_sources-NOTFOUND` to "no additional sources"
    set(f128_sources "")
  endif ()
endif ()


# When a target depends on an object library, CMake seems to try to only build
# the object files that the target actual needs. Before having analyzed the module
# dependencies, CMake assumes there are no module-dependencies.
# To ensure that the module files are built, insert a custom target that is
# opaque to CMake so the library has to be built regardless.
# Dependees on module files must add a target-level dependency on the barrier,
# and file-level dependencies on source files via target_link_libraries
# on the OBJECT libray. The target-level dependency ensures that the module
# files are built when they do not exist yet, the file-level dependency ensures
# that dependees are rebuilt when the module file changes.
function (add_module_barrier barriername objlib)
  add_custom_target(${barriername})
  add_dependencies(${barriername} ${objlib})
endfunction ()

# Build module files if requested.
# The object files written by Flang for these are unused. In the future parts
# of flang-rt may itself be implemented in Fortran in which case these Fortran
# sources need to be added to ${sources} to be included in
# libflang_rt.runtime{.a/.so}. If they also provide an importable .mod, add them
# to flang_module_target(... PUBLIC).
if (FLANG_RT_FORTRAN_MODULES)
  add_flangrt_library(flang-rt-mod OBJECT
    ${module_sources}
  )
  flang_module_target(flang-rt-mod PUBLIC)
  add_module_barrier(flang-rt-mod.barrier flang-rt-mod)

  if (RUNTIMES_FORTRAN_MODULES)
    set(_mod_install_dest "${RUNTIMES_INSTALL_RESOURCE_MOD_PATH}/..")
    cmake_path(NORMAL_PATH _mod_install_dest)
    install(DIRECTORY "${RUNTIMES_OUTPUT_RESOURCE_MOD_DIR}"
      DESTINATION "${_mod_install_dest}"
      COMPONENT flang-rt-mod
      EXCLUDE_FROM_ALL
    )

    if (NOT CMAKE_CONFIGURATION_TYPES)
      add_custom_target(install-flang-rt-mod
        DEPENDS flang-rt-mod
        COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_BINARY_DIR}"
                --component flang-rt-mod)
      add_custom_target(install-flang-rt-mod-stripped
        DEPENDS flang-rt-mod
        COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_BINARY_DIR}"
                --component flang-rt-mod --strip)
    endif ()
  endif ()
endif ()


# Skip building libraries if not requested
if (NOT FLANG_RT_ENABLE_STATIC AND NOT FLANG_RT_ENABLE_SHARED)
  return ()
endif ()

if ("${LLVM_DEFAULT_TARGET_TRIPLE}" MATCHES "^amdgcn|^nvptx")
  set(sources ${gpu_sources})
elseif(FLANG_RT_EXPERIMENTAL_OFFLOAD_SUPPORT STREQUAL "CUDA")
  set(sources ${supported_sources})
else ()
  set(sources ${supported_sources} ${host_sources} ${f128_sources})
endif ()


if (NOT WIN32)
  add_flangrt_library(flang_rt.runtime STATIC SHARED
    ${sources}
    LINK_LIBRARIES ${Backtrace_LIBRARY}
    INSTALL_WITH_TOOLCHAIN
    ADDITIONAL_HEADERS ${public_headers} ${private_headers}
  )

  enable_cuda_compilation(flang_rt.runtime "${sources}")

  # Select a default runtime, which is used for unit and regression tests.
  if (TARGET flang_rt.runtime.default)
    get_target_property(default_target flang_rt.runtime.default ALIASED_TARGET)
    add_library(flang_rt.runtime.unittest ALIAS "${default_target}")
  endif ()
else()
  # Target for building all versions of the runtime
  add_custom_target(flang_rt.runtime)
  set_target_properties(flang_rt.runtime PROPERTIES FOLDER "Flang-RT/Meta")

  function (add_win_flangrt_runtime libtype suffix msvc_lib)
    set(name "flang_rt.runtime.${suffix}")
    add_flangrt_library(${name} ${libtype}
        ${sources}
        ${ARGN}
        LINK_LIBRARIES ${Backtrace_LIBRARY}
        ADDITIONAL_HEADERS ${public_headers} ${private_headers}
      )

    if (msvc_lib)
      set_target_properties(${name}
          PROPERTIES
            MSVC_RUNTIME_LIBRARY "${msvc_lib}"
        )
    endif ()

    # Setting an unique Fortran_MODULE_DIRECTORY is required for each variant to
    # write a different .mod file.
    set_target_properties(${name}
        PROPERTIES
          Fortran_MODULE_DIRECTORY "module.${suffix}"
      )

    enable_cuda_compilation(${name} "${supported_sources}")
    add_dependencies(flang_rt.runtime ${name})
  endfunction ()

  # Variants of the static flang_rt for different versions of the msvc runtime.
  #
  # The dynamic/dynamic_dbg variants are not DLLs themselves, only require
  # linking to msvcrt(d).dll.
  # FIXME: Generating actual runtime DLLs is currently not possible. There are
  # two roadblocks:
  #
  #  * Flang emits /DEFAULTLIB:flang_rt.dynamic.lib into
  #    iso_fortran_env_impl.f90.obj. Because that file is itself part of
  #    flang_rt.dynamic, this results in a recursive dependency when invoking
  #    the linker.
  #
  #  * The externally-visible functions must either be annotated with
  #    __declspec(dllexport), or listed in an exports file. A possible workaround
  #    is CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS which would also export the internal
  #    C++ symbols and still requires global data symbols to be annotated
  #    manually.
  add_win_flangrt_runtime(STATIC static      MultiThreaded         INSTALL_WITH_TOOLCHAIN)
  add_win_flangrt_runtime(STATIC static_dbg  MultiThreadedDebug    INSTALL_WITH_TOOLCHAIN)
  add_win_flangrt_runtime(STATIC dynamic     MultiThreadedDLL      INSTALL_WITH_TOOLCHAIN)
  add_win_flangrt_runtime(STATIC dynamic_dbg MultiThreadedDebugDLL INSTALL_WITH_TOOLCHAIN)

  # Unittests link against LLVMSupport. If CMAKE_MSVC_RUNTIME_LIBRARY is set,
  # that will have been used for LLVMSupport so it must also be used here.
  # Otherwise this will use CMake's default runtime library selection, which
  # is either MultiThreadedDLL or MultiThreadedDebugDLL depending on the configuration.
  # They have to match or linking will fail.
  if (GENERATOR_IS_MULTI_CONFIG)
    # We cannot select an ALIAS library because it may be different
    # per configuration. Fallback to CMake's default.
    add_win_flangrt_runtime(STATIC unittest "" EXCLUDE_FROM_ALL)
  else ()
    # Check if CMAKE_MSVC_RUNTIME_LIBRARY was set.
    if (CMAKE_MSVC_RUNTIME_LIBRARY STREQUAL "MultiThreaded")
        add_library(flang_rt.runtime.unittest ALIAS flang_rt.runtime.static)
    elseif (CMAKE_MSVC_RUNTIME_LIBRARY STREQUAL "MultiThreadedDLL")
        add_library(flang_rt.runtime.unittest ALIAS flang_rt.runtime.dynamic)
    elseif (CMAKE_MSVC_RUNTIME_LIBRARY STREQUAL "MultiThreadedDebug")
        add_library(flang_rt.runtime.unittest ALIAS flang_rt.runtime.static_dbg)
    elseif (CMAKE_MSVC_RUNTIME_LIBRARY STREQUAL "MultiThreadedDebugDLL")
        add_library(flang_rt.runtime.unittest ALIAS flang_rt.runtime.dynamic_dbg)
    else()
      # Default based on the build type.
      string(TOLOWER ${CMAKE_BUILD_TYPE} build_type)
      if (build_type STREQUAL "debug")
          add_library(flang_rt.runtime.unittest ALIAS flang_rt.runtime.dynamic_dbg)
      else ()
          add_library(flang_rt.runtime.unittest ALIAS flang_rt.runtime.dynamic)
      endif ()
    endif()
  endif ()
endif()
