From 8e132f78bfb070effd247e85b19e3c84b9e84bac Mon Sep 17 00:00:00 2001 From: Michael Kruse Date: Tue, 21 Apr 2026 11:58:43 +0100 Subject: [PATCH] [runtimes][CMake] Move Fortran support code from flang-rt (#171610) Common CMake code to be used by flang-rt and openmp to emit Flang module files. Most of the code is not yet used within this PR. Extracted out of #171515 for review by @petrhosek. --- cmake/Modules/GetToolchainDirs.cmake | 11 ++ flang-rt/cmake/modules/AddFlangRT.cmake | 14 ++ runtimes/CMakeLists.txt | 15 ++ runtimes/cmake/config-Fortran.cmake | 250 ++++++++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 runtimes/cmake/config-Fortran.cmake diff --git a/cmake/Modules/GetToolchainDirs.cmake b/cmake/Modules/GetToolchainDirs.cmake index c1f0f70b3e7c..7568734e3f25 100644 --- a/cmake/Modules/GetToolchainDirs.cmake +++ b/cmake/Modules/GetToolchainDirs.cmake @@ -49,6 +49,17 @@ function (get_toolchain_fortran_module_subdir outvar) endfunction () +# Corresponds to Flang's ToolChain::getDefaultIntrinsicModuleDir(). +function (get_toolchain_module_subdir outvar) + set(outval "finclude/flang") + + get_toolchain_target_dirname(arch_dirname) + set(outval "${outval}/${arch_dirname}") + + set(${outvar} "${outval}" PARENT_SCOPE) +endfunction () + + # Corresponds to Clang's ToolChain::getOSLibName(). Adapted from Compiler-RT. function (get_toolchain_os_dirname outvar) if (ANDROID) diff --git a/flang-rt/cmake/modules/AddFlangRT.cmake b/flang-rt/cmake/modules/AddFlangRT.cmake index e46104ab1df5..efb545509c95 100644 --- a/flang-rt/cmake/modules/AddFlangRT.cmake +++ b/flang-rt/cmake/modules/AddFlangRT.cmake @@ -345,6 +345,20 @@ function (add_flangrt_library name) # 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) + # FIXME: RUNTIMES_OUTPUT_RESOURCE_LIB_DIR is not a good location for + # shared libraries because it is not a ld.so default search path. + # Also, the machine where the executable is eventually executed may + # not be one where the compiler is installed, so even RPATH/RUNPATH + # will not help. The most appropriate location for shared libraries + # is /usr/lib//lib.so, like e.g. libgcc_s.so. + # Flang-RT also would require a library versioning scheme so + # executables compiled with different versions of Flang either use + # matching versions of Flang-RT, or use a newer backward-compatible + # version. Currently, Flang-RT has no ABI backwards-compatibility + # policy. + # Currently, we just emit it into RUNTIMES_OUTPUT_RESOURCE_LIB_DIR + # like the static library, which is already in the driver's and + # linker's search path. set_target_properties(${tgtname} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${RUNTIMES_OUTPUT_RESOURCE_LIB_DIR}" diff --git a/runtimes/CMakeLists.txt b/runtimes/CMakeLists.txt index eba41b8b10ac..75551ba0c651 100644 --- a/runtimes/CMakeLists.txt +++ b/runtimes/CMakeLists.txt @@ -270,6 +270,21 @@ cmake_path(NORMAL_PATH RUNTIMES_OUTPUT_RESOURCE_LIB_DIR) cmake_path(NORMAL_PATH RUNTIMES_INSTALL_RESOURCE_LIB_PATH) +# Code for Fortran support. +# +# Currently, only flang-rt and openmp contain Fortran sources. We assume that +# none of the public CMake variables defined in config-Fortran are used unless +# we enable one of these runtimes. Other runtimes would make used of undefined +# variables. +# +# config-Fortran.cmake makes use of +# RUNTIMES_OUTPUT_RESOURCE_DIR, RUNTIMES_INSTALL_RESOURCE_PATH, +# hence must be included after those variables are defined. +if ("flang-rt" IN_LIST LLVM_ENABLE_RUNTIMES OR "openmp" IN_LIST LLVM_ENABLE_RUNTIMES) + include(config-Fortran) +endif () + + option(LLVM_INCLUDE_TESTS "Generate build targets for the runtimes unit tests." ON) option(LLVM_INCLUDE_DOCS "Generate build targets for the runtimes documentation." ON) option(LLVM_ENABLE_SPHINX "Use Sphinx to generate the runtimes documentation." OFF) diff --git a/runtimes/cmake/config-Fortran.cmake b/runtimes/cmake/config-Fortran.cmake new file mode 100644 index 000000000000..9a1f8b4183dd --- /dev/null +++ b/runtimes/cmake/config-Fortran.cmake @@ -0,0 +1,250 @@ +# This file sets the following public CMake variables: +# +# RUNTIMES_ENABLE_FORTRAN - Whether support for Fortran code is available and +# enabled. This is currently not intended to be a user-configuration but +# derived from CMAKE_Fortran_COMPILER. Can also be OFF when Fortran support is +# not needed or is insufficient, e.g. if intrinsic modules are missing and +# cannot be compiled on-the-fly. +# +# RUNTIMES_FORTRAN_BUILD_DEPS - If RUNTIMES_ENABLE_FORTRAN is true, this is a +# list of dependencies that must be built before any Fortran source can be +# compiled. Contains the build targets for intrinsic modules, if necessary. +# Otherweise, it is empty. +# +# RUNTIMES_ENABLE_FLANG_MODULES - Whether to build Flang modules and emit them +# into Flang's search path. This is a CMake CACHE option defined in +# config-Fortran.cmake and default to ON iff the Fortran compiler is detected +# for be a (compatible) version of Flang. In the OFF setting, modules are still +# built, but not installed or emitted into a default path. +# +# RUNTIMES_OUTPUT_RESOURCE_MOD_DIR - Where to emit intrinsic module files in +# the build directory. Most relevant when RUNTIMES_ENABLE_FLANG_MODULES is ON. +# +# RUNTIMES_INSTALL_RESOURCE_MOD_PATH - Where to install intrinsic module files +# in the install prefix. Relative to CMAKE_INSTALL_PREFIX. Only used when +# RUNTIMES_ENABLE_FLANG_MODULES is ON. + + +# Check whether the Fortran compiler already has access to builtin modules. Sets +# HAVE_FORTRAN_INTRINSIC_MODS when returning. +# +# This must be wrapped in a function because +# cmake_push_check_state/cmake_pop_check_state is insufficient to isolate +# a compiler introspection environment, see +# https://gitlab.kitware.com/cmake/cmake/-/issues/27419 +function (check_fortran_builtins_available) + if (CMAKE_Fortran_COMPILER_FORCED AND CMAKE_Fortran_COMPILER_ID STREQUAL "LLVMFlang") + # CMake's check_fortran_source_compiles/try_compile does not take a + # user-defined CMAKE_Fortran_PREPROCESS_SOURCE into account. Instead of + # test-compiling, ask Flang directly for the builtin module files. + # CMAKE_Fortran_PREPROCESS_SOURCE is defined for CMake < 3.24 because it + # does not natively recognize Flang (see below). Once we bump the required + # CMake version, and because setting CMAKE_Fortran_PREPROCESS_SOURCE has + # been deprecated by CMake, this workaround can be removed. + if (NOT DEFINED FORTRAN_HAS_ISO_C_BINDING_MOD) + message(STATUS "Performing Test ISO_C_BINDING_PATH") + execute_process( + COMMAND ${CMAKE_Fortran_COMPILER} ${CMAKE_Fortran_FLAGS} "-print-file-name=iso_c_binding.mod" + OUTPUT_VARIABLE ISO_C_BINDING_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + set(FORTRAN_HAS_ISO_C_BINDING_MOD "") + if (EXISTS "${ISO_C_BINDING_PATH}") + message(STATUS "Performing Test ISO_C_BINDING_PATH -- Success") + set(FORTRAN_HAS_ISO_C_BINDING_MOD TRUE CACHE INTERNAL "Existence result of ${CMAKE_Fortran_COMPILER} -print-file-name=iso_c_binding.mod") + else () + message(STATUS "Performing Test ISO_C_BINDING_PATH -- Failed") + set(FORTRAN_HAS_ISO_C_BINDING_MOD FALSE CACHE INTERNAL "Existence result of ${CMAKE_Fortran_COMPILER} -print-file-name=iso_c_binding.mod") + endif () + endif () + else () + cmake_push_check_state(RESET) + set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") + check_fortran_source_compiles(" + subroutine testroutine + use iso_c_binding + end subroutine + " FORTRAN_HAS_ISO_C_BINDING_MOD SRC_EXT F90) + cmake_pop_check_state() + endif () + set(HAVE_FORTRAN_INTRINSIC_MODS "${FORTRAN_HAS_ISO_C_BINDING_MOD}" PARENT_SCOPE) +endfunction () + + +# Workarounds for older versions of CMake not recognizing FLang. Hence, we +# cannot use CMAKE_Fortran_COMPILER_ID. +cmake_path(GET CMAKE_Fortran_COMPILER STEM _Fortran_COMPILER_STEM) +if (_Fortran_COMPILER_STEM STREQUAL "flang-new" OR _Fortran_COMPILER_STEM STREQUAL "flang") + # CMake 3.24 is the first version of CMake that directly recognizes Flang. + # LLVM's requirement is only CMake 3.20, teach CMake 3.20-3.23 how to use Flang, if used. + if (CMAKE_VERSION VERSION_LESS "3.24") + include(CMakeForceCompiler) + CMAKE_FORCE_Fortran_COMPILER("${CMAKE_Fortran_COMPILER}" "LLVMFlang") + + set(CMAKE_Fortran_COMPILER_ID "LLVMFlang") + set(CMAKE_Fortran_COMPILER_VERSION "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}") + + set(CMAKE_Fortran_SUBMODULE_SEP "-") + set(CMAKE_Fortran_SUBMODULE_EXT ".mod") + + set(CMAKE_Fortran_PREPROCESS_SOURCE + " -cpp -E > ") + + set(CMAKE_Fortran_FORMAT_FIXED_FLAG "-ffixed-form") + set(CMAKE_Fortran_FORMAT_FREE_FLAG "-ffree-form") + + set(CMAKE_Fortran_MODDIR_FLAG "-J") + + set(CMAKE_Fortran_COMPILE_OPTIONS_PREPROCESS_ON "-cpp") + set(CMAKE_Fortran_COMPILE_OPTIONS_PREPROCESS_OFF "-nocpp") + set(CMAKE_Fortran_POSTPROCESS_FLAG "-ffixed-line-length-72") + + set(CMAKE_Fortran_LINKER_WRAPPER_FLAG "-Wl,") + set(CMAKE_Fortran_LINKER_WRAPPER_FLAG_SEP ",") + + set(CMAKE_Fortran_VERBOSE_FLAG "-v") + + set(CMAKE_Fortran_LINK_MODE DRIVER) + endif () + + # Optimization flags are only passed after CMake 3.27.4 + # https://gitlab.kitware.com/cmake/cmake/-/commit/1140087adea98bd8d8974e4c18979f4949b52c34 + if (CMAKE_VERSION VERSION_LESS "3.27.4") + string(APPEND CMAKE_Fortran_FLAGS_DEBUG_INIT " -O0 -g") + string(APPEND CMAKE_Fortran_FLAGS_RELWITHDEBINFO_INIT " -O2 -g") + string(APPEND CMAKE_Fortran_FLAGS_RELEASE_INIT " -O3") + endif () + + # Only CMake 3.28+ pass --target= to Flang. But for cross-compiling, including + # to nvptx amd amdgpu targets, passing the target triple is essential. + # https://gitlab.kitware.com/cmake/cmake/-/commit/e9af7b968756e72553296ecdcde6f36606a0babf + if (CMAKE_VERSION VERSION_LESS "3.28") + set(CMAKE_Fortran_COMPILE_OPTIONS_TARGET "--target=") + endif () +endif () + + +set(RUNTIMES_ENABLE_FORTRAN OFF) + +# Insert at least one element for +# +# add_dependencies(target ${RUNTIMES_FORTRAN_BUILD_DEPS}) +# +# to not fail +add_custom_target(fortran-dummy-dep) +set(RUNTIMES_FORTRAN_BUILD_DEPS fortran-dummy-dep) + +include(CheckLanguage) +check_language(Fortran) +if (CMAKE_Fortran_COMPILER) + enable_language(Fortran) + include(CheckFortranSourceCompiles) + + if (CMAKE_Fortran_COMPILER_ID STREQUAL "LLVMFlang" AND "flang-rt" IN_LIST LLVM_ENABLE_RUNTIMES) + # In a bootstrapping build (or any runtimes-build that includes flang-rt), + # the intrinsic modules are not built yet. Targets can depend on + # flang-rt-mod to ensure that flang-rt's modules are built first. + list(APPEND RUNTIMES_FORTRAN_BUILD_DEPS flang-rt-mod) + set(RUNTIMES_ENABLE_FORTRAN ON) + message(STATUS "Fortran support enabled using just-built Flang-RT builtin modules") + else () + # Check whether building modules works, avoid causing the entire build to + # fail because of Fortran. The primary situation we want to support here + # is Flang, or its intrinsic modules were built separately in a + # non-bootstrapping build. + check_fortran_builtins_available() + if (HAVE_FORTRAN_INTRINSIC_MODS) + set(RUNTIMES_ENABLE_FORTRAN ON) + message(STATUS "Fortran support enabled using compiler's own modules") + else () + message(STATUS "Fortran support disabled: Not passing smoke check") + endif () + endif () +else () + message(STATUS "Fortran support disabled: not enabled in CMake; Use CMAKE_Fortran_COMPILER_WORKS=yes if the issues is missing builtin modules") +endif () + + +# Check whether modules files are compatible with our version of Flang. +set(RUNTIMES_ENABLE_FLANG_MODULES_default OFF) +if (CMAKE_Fortran_COMPILER_ID STREQUAL "LLVMFlang") + set(RUNTIMES_ENABLE_FLANG_MODULES_default ON) +else () + set(RUNTIMES_ENABLE_FLANG_MODULES_default OFF) +endif () +option(RUNTIMES_ENABLE_FLANG_MODULES "Make Fortran .mod files available to Flang; should only be enabled if compiling with a matching version of Flang" "${RUNTIMES_ENABLE_FLANG_MODULES_default}") + + +# Determine the paths for Fortran .mod files. +if (RUNTIMES_ENABLE_FLANG_MODULES) + # Flang expects its builtin modules in Clang's resource directory. + get_toolchain_module_subdir(toolchain_mod_subdir) + extend_path(RUNTIMES_OUTPUT_RESOURCE_MOD_DIR "${RUNTIMES_OUTPUT_RESOURCE_DIR}" "${toolchain_mod_subdir}") + extend_path(RUNTIMES_INSTALL_RESOURCE_MOD_PATH "${RUNTIMES_INSTALL_RESOURCE_PATH}" "${toolchain_mod_subdir}") + cmake_path(NORMAL_PATH RUNTIMES_INSTALL_RESOURCE_MOD_PATH) + + # No way to find out which mod files are built by a target, so install the + # entire output directory. flang_module_target() will copy (only) the PUBLIC + # .mod file into the output directory. + # https://stackoverflow.com/questions/52712416/cmake-fortran-module-directory-to-be-used-with-add-library + set(destination "${RUNTIMES_INSTALL_RESOURCE_MOD_PATH}/..") + cmake_path(NORMAL_PATH destination) + install(DIRECTORY "${RUNTIMES_OUTPUT_RESOURCE_MOD_DIR}" + DESTINATION "${destination}" + ) +else () + # If Flang modules are disabled (e.g. because the compiler is not Flang), avoid the risk of Flang accidentally picking them up. + extend_path(RUNTIMES_OUTPUT_RESOURCE_MOD_DIR "${CMAKE_CURRENT_BINARY_DIR}" "finclude-${CMAKE_Fortran_COMPILER_ID}") + + # We don't know how to install modules for other compilers. Do not install them at all. + set(RUNTIMES_INSTALL_RESOURCE_MOD_PATH "") +endif () +cmake_path(NORMAL_PATH RUNTIMES_OUTPUT_RESOURCE_MOD_DIR) + + +# Set options to compile Fortran module files. Assumes the code above has run. +# +# Usage: +# +# flang_module_target(name +# PUBLIC +# Modules files are to be used by other Fortran sources. If a library is +# compiled multiple times (e.g. static/shared, or msvcrt variants), only +# one of those can be public module files; non-public modules are still +# generated but to be forgotten inside the build directory to not +# conflict with each other. +# Also, installs the module with the toolchain. +# ) +function (flang_module_target tgtname) + set(options PUBLIC) + cmake_parse_arguments(ARG + "${options}" + "" + "" + ${ARGN} + ) + + # Let all modules find the public module files + target_compile_options(${tgtname} PRIVATE + "$<$:-fintrinsic-modules-path=${RUNTIMES_OUTPUT_RESOURCE_MOD_DIR}>" + ) + + if (CMAKE_Fortran_COMPILER_ID MATCHES "LLVM") + target_compile_options(${tgtname} PRIVATE + # Flang bug workaround: Reformating of cooked token buffer causes + # identifier to be split between lines + "$<$:SHELL:-Xflang;SHELL:-fno-reformat>" + ) + endif () + + if (ARG_PUBLIC) + set_target_properties(${tgtname} + PROPERTIES + Fortran_MODULE_DIRECTORY "${RUNTIMES_OUTPUT_RESOURCE_MOD_DIR}" + ) + else () + # Keep non-public modules where CMake would put them normally; + # Modules of different target must not overwrite each other. + endif () +endfunction ()