#===-- cmake/modules/AddFlangRT.cmake --------------------------------------===#
#
# 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
#
#===------------------------------------------------------------------------===#

# Builds a library with common options for Flang-RT.
#
# Usage:
#
# add_flangrt_library(name sources ...
#   SHARED
#     Build a dynamic (.so/.dll) library
#   STATIC
#     Build a static (.a/.lib) library
#   OBJECT
#     Always create an object library.
#     Without SHARED/STATIC, build only the object library.
#   INSTALL_WITH_TOOLCHAIN
#     Install library into Clang's resource directory so it can be found by the
#     Flang driver during compilation, including tests
#   EXCLUDE_FROM_ALL
#     Do not build library by default; typically used for libraries needed for
#     testing only, no install
#   LINK_TO_LLVM
#     Library requires include path and linking to LLVM's Support component
#   ADDITIONAL_HEADERS
#     May specify header files for IDE generators.
#   INCLUDE_DIRECTORIES
#     Additional target_include_directories for all added targets
#   LINK_LIBRARIES
#     Additional target_link_libraries for all added targets
#   TARGET_PROPERTIES
#     Set target properties of all added targets
# )
function (add_flangrt_library name)
  set(options STATIC SHARED OBJECT INSTALL_WITH_TOOLCHAIN EXCLUDE_FROM_ALL LINK_TO_LLVM)
  set(multiValueArgs ADDITIONAL_HEADERS INCLUDE_DIRECTORIES LINK_LIBRARIES TARGET_PROPERTIES)
  cmake_parse_arguments(ARG
    "${options}"
    ""
    "${multiValueArgs}"
    ${ARGN})

  if (ARG_INSTALL_WITH_TOOLCHAIN AND ARG_EXCLUDE_FROM_ALL)
    message(SEND_ERROR "add_flangrt_library(${name} ...):
        INSTALL_WITH_TOOLCHAIN and EXCLUDE_FROM_ALL are in conflict. When
        installing an artifact it must have been built first in the 'all' target.
      ")
  endif ()

  # Internal names of libraries. If called with just single type option, use
  # the default name for it. Name of targets must only depend on function
  # arguments to be predictable for callers.
  set(name_static "${name}.static")
  set(name_shared "${name}.shared")
  set(name_object "obj.${name}")
  if (ARG_STATIC AND NOT ARG_SHARED)
    set(name_static "${name}")
  elseif (NOT ARG_STATIC AND ARG_SHARED)
    set(name_shared "${name}")
  elseif (NOT ARG_STATIC AND NOT ARG_SHARED AND ARG_OBJECT)
    set(name_object "${name}")
  elseif (NOT ARG_STATIC AND NOT ARG_SHARED AND NOT ARG_OBJECT)
    # Only one of them will actually be built.
    set(name_static "${name}")
    set(name_shared "${name}")
  endif ()

  # Determine what to build. If not explicitly specified, honor
  # BUILD_SHARED_LIBS (e.g. for unittest libraries). If can build static and
  # shared, use ENABLE_STATIC/ENABLE_SHARED setting.
  if (ARG_STATIC AND ARG_SHARED)
    set(build_static ${FLANG_RT_ENABLE_STATIC})
    set(build_shared ${FLANG_RT_ENABLE_SHARED})
  else ()
    set(build_static ${ARG_STATIC})
    set(build_shared ${ARG_SHARED})
  endif ()
  if (NOT ARG_STATIC AND NOT ARG_SHARED AND NOT ARG_OBJECT)
    if (BUILD_SHARED_LIBS)
      set(build_shared ON)
    else ()
      set(build_static ON)
    endif ()
  endif ()

  # Build an object library if building multiple libraries at once or if
  # explicitly requested.
  set(build_object OFF)
  if (ARG_OBJECT)
    set(build_object ON)
  elseif (build_static AND build_shared)
    set(build_object ON)
  endif ()

  # srctargets: targets that contain source files
  # libtargets: static/shared if they are built
  # alltargets: any add_library target added by this function
  set(srctargets "")
  set(libtargets "")
  set(alltargets "")
  if (build_static)
    list(APPEND srctargets "${name_static}")
    list(APPEND libtargets "${name_static}")
    list(APPEND alltargets "${name_static}")
  endif ()
  if (build_shared)
    list(APPEND srctargets "${name_shared}")
    list(APPEND libtargets "${name_shared}")
    list(APPEND alltargets "${name_shared}")
  endif ()
  if (build_object)
    set(srctargets "${name_object}")
    list(APPEND alltargets "${name_object}")
  endif ()

  set(extra_args "")
  if (ARG_EXCLUDE_FROM_ALL)
    list(APPEND extra_args EXCLUDE_FROM_ALL)
  endif ()

  # Also add header files to IDEs to list as part of the library.
  set_source_files_properties(${ARG_ADDITIONAL_HEADERS} PROPERTIES HEADER_FILE_ONLY ON)

  # Create selected library types.
  if (build_object)
    add_library("${name_object}" OBJECT ${extra_args} ${ARG_ADDITIONAL_HEADERS} ${ARG_UNPARSED_ARGUMENTS})
    set_target_properties(${name_object} PROPERTIES
        POSITION_INDEPENDENT_CODE ON
        FOLDER "Flang-RT/Object Libraries"
      )

    # Replace arguments for the libraries we are going to create.
    set(ARG_ADDITIONAL_HEADERS "")
    set(ARG_UNPARSED_ARGUMENTS "$<TARGET_OBJECTS:${name_object}>")
  endif ()
  if (build_static)
    add_library("${name_static}" STATIC ${extra_args} ${ARG_ADDITIONAL_HEADERS} ${ARG_UNPARSED_ARGUMENTS})
    target_link_libraries("${name_static}" PRIVATE flang-rt-libcxx-headers flang-rt-libc-headers flang-rt-libc-static)
  endif ()
  if (build_shared)
    add_library("${name_shared}" SHARED ${extra_args} ${ARG_ADDITIONAL_HEADERS} ${ARG_UNPARSED_ARGUMENTS})
    target_link_libraries("${name_shared}" PRIVATE flang-rt-libcxx-headers flang-rt-libc-headers flang-rt-libc-shared)
    if (Threads_FOUND) 
      target_link_libraries(${name_shared} PUBLIC Threads::Threads)
    endif ()

    # Special dependencies handling for shared libraries only:
    #
    # flang-rt libraries must not depend on libc++/libstdc++,
    # so set the linker language to C to avoid the unnecessary
    # library dependence. Note that libc++/libstdc++ may still
    # come through CMAKE_CXX_IMPLICIT_LINK_LIBRARIES.
    set_target_properties(${name_shared} PROPERTIES LINKER_LANGUAGE C)
    # Use --as-needed to avoid unnecessary dependencies.
    if (LINKER_AS_NEEDED_OPT)
      target_link_options(${name_shared} BEFORE PRIVATE
          "${LINKER_AS_NEEDED_OPT}"
        )
    endif()
  endif ()

  if (libtargets)
    # Provide a default alias which exists in either setting.
    if (BUILD_SHARED_LIBS)
      if (build_shared)
        set(default_target "${name_shared}")
      else ()
        set(default_target "${name_static}")
      endif ()
    else ()
      if (build_static)
        set(default_target "${name_static}")
      else ()
        set(default_target "${name_shared}")
      endif ()
    endif ()
    add_library(${name}.default ALIAS "${default_target}")

    # Provide a build target that builds any enabled library.
    # Not intended for target_link_libraries. Either use the ${name}.static,
    # ${name}.shared variants, or ${name}.default to let BUILD_SHARED_LIBS
    # decide.
    if (NOT TARGET ${name})
      add_custom_target(${name})
      add_dependencies(${name} ${libtargets})
    endif ()
  endif ()

  foreach (tgtname IN LISTS libtargets)
    if (NOT WIN32)
      # Use same stem name for .a and .so. Common in UNIX environments.
      # Not possible in Windows environments.
      set_target_properties(${tgtname} PROPERTIES OUTPUT_NAME "${name}")
    endif ()

    if (ARG_INSTALL_WITH_TOOLCHAIN)
      set_target_properties(${tgtname} PROPERTIES FOLDER "Flang-RT/Toolchain Libraries")
    else ()
      set_target_properties(${tgtname} PROPERTIES FOLDER "Flang-RT/Libraries")
    endif ()
  endforeach ()

 set(TARGET_FLAGS)
  if(APPLE)
    set(DARWIN_EMBEDDED_PLATFORMS)
    set(DARWIN_osx_BUILTIN_MIN_VER 10.7)
    set(DARWIN_osx_BUILTIN_MIN_VER_FLAG
        -mmacosx-version-min=${DARWIN_osx_BUILTIN_MIN_VER})
  endif()

  # Define how to compile and link the library.
  # Some conceptionally only apply to ${srctargets} or ${libtargets}, but we
  # apply them to ${alltargets}. In worst case, they are ignored by CMake.
  foreach (tgtname IN LISTS alltargets)
    # Minimum required C++ version for Flang-RT, even if CMAKE_CXX_STANDARD is defined to something else.
    target_compile_features(${tgtname} PRIVATE cxx_std_17)

    # When building the flang runtime if LTO is enabled the archive file
    # contains LLVM IR rather than object code. Currently flang is not
    # LTO aware so cannot link this file to compiled Fortran code.
    if (FLANG_RT_HAS_FNO_LTO_FLAG)
      target_compile_options(${tgtname} PRIVATE -fno-lto)
    endif ()

    # Use compiler-specific options to disable exceptions and RTTI.
    if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
      target_compile_options(${tgtname} PRIVATE
          $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions -fno-rtti -funwind-tables -fno-asynchronous-unwind-tables>
        )

      # We define our own _GLIBCXX_THROW_OR_ABORT here because, as of
      # GCC 15.1, the libstdc++ header file <bits/c++config> uses
      # (void)_EXC in its definition of _GLIBCXX_THROW_OR_ABORT to
      # silence a warning.
      #
      # This is a problem for us because some compilers, specifically
      # clang, do not always optimize away that (void)_EXC even though
      # it is unreachable since it occurs after a call to
      # _builtin_abort().  Because _EXC is typically an object derived
      # from std::exception, (void)_EXC, when not optimized away,
      # calls std::exception methods defined in the libstdc++ shared
      # library.  We shouldn't link against that library since our
      # build version may conflict with the version used by a hybrid
      # Fortran/C++ application.
      #
      # Redefining _GLIBCXX_THROW_OR_ABORT in this manner is not
      # supported by the maintainers of libstdc++, so future changes
      # to libstdc++ may require future changes to this build script
      # and/or future changes to the Fortran runtime source code.
      target_compile_options(${tgtname} PUBLIC "-D_GLIBCXX_THROW_OR_ABORT(_EXC)=(__builtin_abort())")
    elseif (MSVC)
      target_compile_options(${tgtname} PRIVATE
          $<$<COMPILE_LANGUAGE:CXX>:/EHs-c- /GR->
        )
    elseif (CMAKE_CXX_COMPILER_ID MATCHES "XL")
      target_compile_options(${tgtname} PRIVATE
          $<$<COMPILE_LANGUAGE:CXX>:-qnoeh -qnortti>
        )
    endif ()

    # Add target specific options if necessary.
    if ("${LLVM_RUNTIMES_TARGET}" MATCHES "^amdgcn")
      target_compile_options(${tgtname} PRIVATE
          $<$<COMPILE_LANGUAGE:CXX>:-nogpulib -flto -fvisibility=hidden>
        )
    elseif ("${LLVM_RUNTIMES_TARGET}" MATCHES "^nvptx")
      target_compile_options(${tgtname} PRIVATE
          $<$<COMPILE_LANGUAGE:CXX>:-nogpulib -flto -fvisibility=hidden -Wno-unknown-cuda-version --cuda-feature=+ptx63>
        )
    elseif (APPLE)
      # Clang on Darwin enables non-POSIX extensions by default.
      # This causes some macros to leak, such as HUGE from <math.h>, which
      # causes some conflicts with Flang symbols (but not with Flang-RT, for
      # now).
      # It also causes some Flang-RT extensions to be disabled, such as fdate,
      # that checks for _POSIX_C_SOURCE.
      # Setting _POSIX_C_SOURCE avoids these issues.
      target_compile_options(${tgtname} PRIVATE
          $<$<COMPILE_LANGUAGE:CXX>:${DARWIN_osx_BUILTIN_MIN_VER_FLAG} -D_POSIX_C_SOURCE=200809>
        )
    endif ()

    # Also for CUDA source when compiling with FLANG_RT_EXPERIMENTAL_OFFLOAD_SUPPORT=CUDA
    if (CMAKE_CUDA_COMPILER_ID MATCHES "NVIDIA")
      # Assuming gcc as host compiler.
      target_compile_options(${tgtname} PRIVATE
          $<$<COMPILE_LANGUAGE:CUDA>:--no-exceptions -Xcompiler -fno-rtti -Xcompiler -fno-unwind-tables -Xcompiler -fno-asynchronous-unwind-tables>
        )
    else ()
      # Assuming a clang-compatible CUDA compiler.
      target_compile_options(${tgtname} PRIVATE
          $<$<COMPILE_LANGUAGE:CUDA>:-fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables>
        )
    endif ()

    # Flang-RT's public headers
    target_include_directories(${tgtname} PUBLIC "${FLANG_RT_SOURCE_DIR}/include")

    # For ISO_Fortran_binding.h to be found by the runtime itself (Accessed as #include "flang/ISO_Fortran_binding.h")
    # User applications can use #include <ISO_Fortran_binding.h>
    target_include_directories(${tgtname} PUBLIC "${FLANG_SOURCE_DIR}/include")

    # For Flang-RT's configured config.h to be found
    target_include_directories(${tgtname} PRIVATE "${FLANG_RT_BINARY_DIR}")

    # Disable libstdc++/libc++ assertions, even in an LLVM_ENABLE_ASSERTIONS
    # build, to avoid an unwanted dependency on libstdc++/libc++.so.
    target_compile_definitions(${tgtname} PUBLIC _GLIBCXX_NO_ASSERTIONS)
    if (FLANG_RT_SUPPORTS_UNDEFINE_FLAG)
      target_compile_options(${tgtname} PUBLIC
          "$<$<COMPILE_LANGUAGE:CXX>:-Wp,-U_GLIBCXX_ASSERTIONS>")
      target_compile_options(${tgtname} PUBLIC
          "$<$<COMPILE_LANGUAGE:CXX>:-Wp,-U_LIBCPP_ENABLE_ASSERTIONS>")
    endif ()

    # Non-GTest unittests depend on LLVMSupport
    if (ARG_LINK_TO_LLVM)
      if (LLVM_LINK_LLVM_DYLIB)
        set(llvm_libs LLVM)
      else()
        llvm_map_components_to_libnames(llvm_libs Support)
      endif()
      target_link_libraries(${tgtname} PUBLIC ${llvm_libs})
      target_include_directories(${tgtname} PUBLIC ${LLVM_INCLUDE_DIRS})
    endif ()

    if (ARG_INCLUDE_DIRECTORIES)
      target_include_directories(${tgtname} ${ARG_INCLUDE_DIRECTORIES})
    endif ()

    if (ARG_LINK_LIBRARIES)
      target_link_libraries(${tgtname} PUBLIC ${ARG_LINK_LIBRARIES})
    endif ()
  endforeach ()

  foreach (tgtname IN LISTS libtargets)
    # If this is part of the toolchain, put it into the compiler's resource
    # directory. Otherwise it is part of testing and is not installed at all.
    # TODO: Consider multi-configuration builds (MSVC_IDE, "Ninja Multi-Config")
    if (ARG_INSTALL_WITH_TOOLCHAIN)
      set_target_properties(${tgtname}
        PROPERTIES
          ARCHIVE_OUTPUT_DIRECTORY "${FLANG_RT_OUTPUT_RESOURCE_LIB_DIR}"
          LIBRARY_OUTPUT_DIRECTORY "${FLANG_RT_OUTPUT_RESOURCE_LIB_DIR}"
        )

      install(TARGETS ${tgtname}
          ARCHIVE DESTINATION "${FLANG_RT_INSTALL_RESOURCE_LIB_PATH}"
          LIBRARY DESTINATION "${FLANG_RT_INSTALL_RESOURCE_LIB_PATH}"
        )
    endif ()

    if (ARG_TARGET_PROPERTIES)
      set_target_properties(${tgtname} PROPERTIES ${ARG_TARGET_PROPERTIES})
    endif ()

    # flang-rt should build all the Flang-RT targets that are built in an
    # 'all' build.
    if (NOT ARG_EXCLUDE_FROM_ALL)
      add_dependencies(flang-rt ${tgtname})
    endif ()
  endforeach ()
endfunction (add_flangrt_library)
