diff --git a/.github/workflows/fortran.yml b/.github/workflows/fortran.yml index c047b1ead62..c118c499653 100644 --- a/.github/workflows/fortran.yml +++ b/.github/workflows/fortran.yml @@ -7,6 +7,7 @@ on: paths: - '.github/workflows/fortran.yml' - 'enzyme/Enzyme/**' + - 'enzyme/Fortran/**' - 'enzyme/includes/**' - 'enzyme/test/**' - 'enzyme/tools/**' @@ -20,6 +21,7 @@ on: paths: - '.github/workflows/fortran.yml' - 'enzyme/Enzyme/**' + - 'enzyme/Fortran/**' - 'enzyme/includes/**' - 'enzyme/test/**' - 'enzyme/tools/**' @@ -73,7 +75,7 @@ jobs: - name: generate build system run: | rm -rf build && mkdir build && cd build - cmake ../enzyme -GNinja -DCMAKE_BUILD_TYPE=${{ matrix.build }} -DLLVM_DIR=/usr/lib/llvm-${{ matrix.llvm }}/lib/cmake/llvm -DLLVM_EXTERNAL_LIT=`which lit` -DENZYME_IFX=ON + cmake ../enzyme -GNinja -DCMAKE_BUILD_TYPE=${{ matrix.build }} -DLLVM_DIR=/usr/lib/llvm-${{ matrix.llvm }}/lib/cmake/llvm -DLLVM_EXTERNAL_LIT=`which lit` -DENZYME_FORTRAN=ON -DCMAKE_Fortran_COMPILER=ifx - name: build enzyme working-directory: 'build' run: ninja LLVMEnzyme-${{ matrix.llvm }} diff --git a/enzyme/CMakeLists.txt b/enzyme/CMakeLists.txt index ce533e69cfb..da113754287 100644 --- a/enzyme/CMakeLists.txt +++ b/enzyme/CMakeLists.txt @@ -39,7 +39,6 @@ option(ENZYME_CLANG "Build enzyme clang plugin" ON) option(ENZYME_FLANG "Build enzyme flang symlink" OFF) option(ENZYME_MLIR "Build enzyme mlir plugin" OFF) option(ENZYME_IFX "Enable enzyme support for the Intel Fortran compiler IFX" OFF) -option(ENZYME_IFX "Enable enzyme support for the Intel Fortran compiler IFX" OFF) option(ENZYME_EXTERNAL_SHARED_LIB "Build external shared library" OFF) option(ENZYME_APPLE_DYNAMIC_LOOKUP "On Apple, link Enzyme shared library with -flat_namespace/-undefined,dynamic_lookup instead of linking LLVM" @@ -49,6 +48,7 @@ option(ENZYME_WARN_COMPILER "Warn if enzyme detects potentially incompatible com option(ENZYME_ENABLE_REACTANT "Build support library for Reactant C++ frontend" OFF) set(ENZYME_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(ENZYME_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(Fortran_COMPILER ${CMAKE_Fortran_COMPILER}) list(APPEND CMAKE_MODULE_PATH "${ENZYME_SOURCE_DIR}/cmake/modules") set(LLVM_SHLIBEXT "${CMAKE_SHARED_MODULE_SUFFIX}") @@ -298,12 +298,12 @@ add_subdirectory(Enzyme) if (ENZYME_BC_LOADER) add_subdirectory(BCLoad) endif() -if (ENZYME_ENABLE_PLUGINS) - add_subdirectory(test) -endif() if (ENZYME_FORTRAN) add_subdirectory(Fortran) endif() +if (ENZYME_ENABLE_PLUGINS) + add_subdirectory(test) +endif() # The benchmarks data are not in git-exported source archives to minimize size. # Only add the benchmarks if the directory exists. diff --git a/enzyme/Fortran/CMakeLists.txt b/enzyme/Fortran/CMakeLists.txt index d3e6fdaa139..aac46086e9d 100644 --- a/enzyme/Fortran/CMakeLists.txt +++ b/enzyme/Fortran/CMakeLists.txt @@ -1,8 +1,15 @@ +message("Building Fortran bindings") + enable_language(Fortran) include(FortranCInterface) FortranCInterface_VERIFY(QUIET) -add_library(EnzymeFortran enzyme.f90) +# Initialize Fortran module build output directory for all subsequent targets +if(NOT DEFINED CMAKE_Fortran_MODULE_DIRECTORY) + set(CMAKE_Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/modules") +endif() + +add_library(EnzymeFortran SHARED enzyme.f90 enzyme_function_hooks.f90) # Install library, create target file install( diff --git a/enzyme/Fortran/README.md b/enzyme/Fortran/README.md new file mode 100644 index 00000000000..05f6f4fcbc4 --- /dev/null +++ b/enzyme/Fortran/README.md @@ -0,0 +1,74 @@ +# Fortran bindings for Enzyme + +Source files in this subdirectory provides Fortran bindings for Enzyme, as +detailed in the following. + +## Function hooks + +We provide bindings for the `__enzyme_fwddiff` and `__enzyme_autodiff` function +hooks using implicit interfaces. Some Fortran compilers disallow procedure names +starting with an underscore so we rename the function hooks to remove the +leading double underscore. + +To make use of the `enzyme_autodiff` function hook in your code, import via +```fortran +use enzyme, only: enzyme_autodiff +``` +and call it as a subroutine or function as appropriate. For example, if you have +a function +```fortran + real function square(x) + real, intent(in) :: x + square = x**2 + end function +``` +then you can compute its derivative with reverse mode with the call +```fortran + call enzyme_autodiff(square, x, dx) +``` + +Similarly for +`enzyme_fwddiff`. Thanks to the implicit interface, arbitrary signatures are +supported, with the following caveats. + +> [!NOTE] +> A limitation of the implicit interfacing is that it only works for arguments +> that are passed by reference - the default in Fortran. If you want to pass any +> arguments by value using the `value` attribute then you will need to write an +> explicit interface block to the function hook yourself. + +> [!WARNING] +> The implicit interfacing approach is not supported by the Intel Fortran +> compiler ifx when running without optimizations, i.e., running with `-O0`. If +> you want to use ifx with `-O0` then you will need to write an explicit +> interface block, even if you are only passing arguments by reference. + +> [!WARNING] +> Differentiation with respect to procedures with assumed shape arrays is not +> currently supported when compiling with Flang. It should work with ifx, +> however. + +## Activity descriptors + +We provide bindings for the activity descriptors `enzyme_const`, `enzyme_dup`, +`enzyme_dupnoneed`, and `enzyme_out`, as well as the descriptors +`enzyme_scalar`, `enzyme_width`, and `enzyme_vector`. To make use of these in +your code, import via +```fortran +use enzyme, only: enzyme_const, enzyme_dup +``` +and then include them in calls to function hooks as you would in C or C++. For +example, if you have a subroutine +```fortran + subroutine my_subroutine(n, x, y) + integer, intent(in) :: n + real, dimension(n), intent(in) :: x + real, dimension(n), intent(out) :: y + ! ... + end subroutine my_subroutine +``` +then you can make use of activity descriptors like so: +```fortran + call enzyme_autodiff(my_subroutine, enzyme_const, n, & + enzyme_dup, x, dx, enzyme_dup, y, dy +``` diff --git a/enzyme/Fortran/enzyme.f90 b/enzyme/Fortran/enzyme.f90 index 1e999ff1004..a5bd9bad0de 100644 --- a/enzyme/Fortran/enzyme.f90 +++ b/enzyme/Fortran/enzyme.f90 @@ -22,6 +22,8 @@ ! ===----------------------------------------------------------------------=== ! module enzyme use iso_c_binding, only: c_int + use enzyme_function_hooks, only: enzyme_autodiff => f__enzyme_autodiff, & + enzyme_fwddiff => f__enzyme_fwddiff implicit none private @@ -33,4 +35,8 @@ module enzyme integer(c_int), public, bind(C, name="enzyme_scalar") :: enzyme_scalar integer(c_int), public, bind(C, name="enzyme_width") :: enzyme_width integer(c_int), public, bind(C, name="enzyme_vector") :: enzyme_vector + + ! Bindings for function hooks + public :: enzyme_autodiff + public :: enzyme_fwddiff end module enzyme diff --git a/enzyme/Fortran/enzyme_function_hooks.f90 b/enzyme/Fortran/enzyme_function_hooks.f90 new file mode 100644 index 00000000000..714fd415dbd --- /dev/null +++ b/enzyme/Fortran/enzyme_function_hooks.f90 @@ -0,0 +1,39 @@ +! ===- enzyme_function_hooks.f90 - Fortran bindings function hooks --------=== ! +! +! Enzyme Project +! +! Part of the Enzyme 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 +! +! If using this code in an academic setting, please cite the following: +! @misc{enzymeGithub, +! author = {William S. Moses and Valentin Churavy}, +! title = {Enzyme: High Performance Automatic Differentiation of LLVM}, +! year = {2020}, +! howpublished = {\url{https://github.com/wsmoses/Enzyme}}, +! note = {commit xxxxxxx} +! } +! +! ===----------------------------------------------------------------------=== ! +! +! This file provides Fortran bindings for Enzyme's function hooks. +! +! The fact that the double-underscore function hook names appears in the +! implicit interfaces defined in this module is sufficient to get Enzyme to be +! applied in the appropriate way. We provide cleaner bindings without +! double-underscores in the main Fortran Enzyme module in enzyme.f90. +! +! ===----------------------------------------------------------------------=== ! +module enzyme_function_hooks + implicit none + private + + ! Bindings for function hooks + ! NOTE: Leading underscores are not permitted by some Fortran compilers so we + ! prepend with 'f' in the Fortran versions of the function hooks. + public :: f__enzyme_autodiff + public :: f__enzyme_fwddiff + external :: f__enzyme_autodiff + external :: f__enzyme_fwddiff +end module enzyme_function_hooks diff --git a/enzyme/test/BUILD b/enzyme/test/BUILD index 9ec7dda4641..0b480eb41e0 100644 --- a/enzyme/test/BUILD +++ b/enzyme/test/BUILD @@ -26,6 +26,7 @@ expand_template( "@TARGET_TRIPLE@": "", "@TARGETS_TO_BUILD@": "ALL", "@LLVM_SHLIBEXT@": ".so", + "@Fortran_COMPILER@": "", }, template = "lit.site.cfg.py.in", visibility = [":__subpackages__"], diff --git a/enzyme/test/CMakeLists.txt b/enzyme/test/CMakeLists.txt index 8f781a0f04c..dcfc38492b5 100644 --- a/enzyme/test/CMakeLists.txt +++ b/enzyme/test/CMakeLists.txt @@ -20,7 +20,8 @@ add_custom_target(test-cmake COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/test_cma endif() endif() endif() -if (ENZYME_IFX) +if (ENZYME_FORTRAN) + list(APPEND ENZYME_TEST_DEPS EnzymeFortran) add_subdirectory(Fortran) endif() if (ENZYME_BC_LOADER) diff --git a/enzyme/test/Fortran/CMakeLists.txt b/enzyme/test/Fortran/CMakeLists.txt index 9487564e769..975cca7940b 100644 --- a/enzyme/test/Fortran/CMakeLists.txt +++ b/enzyme/test/Fortran/CMakeLists.txt @@ -1,3 +1,5 @@ +message("Building Fortran tests") + add_subdirectory(ForwardMode) add_subdirectory(ReverseMode) diff --git a/enzyme/test/Fortran/ReverseMode/square.f90 b/enzyme/test/Fortran/ReverseMode/square.f90 index 8182b38adda..6875df196a5 100644 --- a/enzyme/test/Fortran/ReverseMode/square.f90 +++ b/enzyme/test/Fortran/ReverseMode/square.f90 @@ -1,21 +1,9 @@ -! RUN: if [ %llvmver -ge 13 ]; then ifx -flto -O0 -c %s -o /dev/stdout | %opt %loadEnzyme %enzyme -o %t && ifx -flto -O0 %t -o %t1 && %t1 | FileCheck %s; fi -! RUN: if [ %llvmver -ge 13 ]; then ifx -flto -O1 -c %s -o /dev/stdout | %opt %loadEnzyme %enzyme -o %t && ifx -flto -O1 %t -o %t1 && %t1 | FileCheck %s; fi -! RUN: if [ %llvmver -ge 13 ]; then ifx -flto -O2 -c %s -o /dev/stdout | %opt %loadEnzyme %enzyme -o %t && ifx -flto -O2 %t -o %t1 && %t1 | FileCheck %s; fi -! RUN: if [ %llvmver -ge 13 ]; then ifx -flto -O3 -c %s -o /dev/stdout | %opt %loadEnzyme %enzyme -o %t && ifx -flto -O3 %t -o %t1 && %t1 | FileCheck %s; fi +! TODO: if [ %llvmver -ge 13 ]; then %fc -flto -O0 -c %loadFortran %s -o /dev/stdout | %opt %loadEnzyme %enzyme -o %t && %fc -flto -O0 %t -o %t1 && %t1 | FileCheck %s; fi +! RUN: if [ %llvmver -ge 13 ]; then %fc -flto -O1 -c %loadFortran %s -o /dev/stdout | %opt %loadEnzyme %enzyme -o %t && %fc -flto -O1 %t -o %t1 && %t1 | FileCheck %s; fi +! RUN: if [ %llvmver -ge 13 ]; then %fc -flto -O2 -c %loadFortran %s -o /dev/stdout | %opt %loadEnzyme %enzyme -o %t && %fc -flto -O2 %t -o %t1 && %t1 | FileCheck %s; fi +! RUN: if [ %llvmver -ge 13 ]; then %fc -flto -O3 -c %loadFortran %s -o /dev/stdout | %opt %loadEnzyme %enzyme -o %t && %fc -flto -O3 %t -o %t1 && %t1 | FileCheck %s; fi module math - interface - subroutine square__enzyme_autodiff(fn, x, dx) - interface - real function fn_decal(a) - real, intent(in) :: a - end function - end interface - procedure(fn_decal) :: fn - real, intent(in) :: x - real, intent(inout) :: dx - end subroutine - end interface contains real function square( x ) real, intent(in) :: x @@ -24,7 +12,8 @@ real function square( x ) end module math program app - use math + use enzyme, only: enzyme_autodiff + use math, only: square implicit none real :: x, dx @@ -32,7 +21,7 @@ program app print *, square(x) dx = 0 - call square__enzyme_autodiff(square, x, dx); + call enzyme_autodiff(square, x, dx) print *, dx end program app diff --git a/enzyme/test/lit.site.cfg.py.in b/enzyme/test/lit.site.cfg.py.in index 0cc5e6f28f3..dafafca769a 100644 --- a/enzyme/test/lit.site.cfg.py.in +++ b/enzyme/test/lit.site.cfg.py.in @@ -97,6 +97,8 @@ config.substitutions.append(('%OPnewLoadEnzyme', newPMOP)) config.substitutions.append(('%enzyme', ('-enzyme' if int(config.llvm_ver) < 16 else '-passes="enzyme"'))) config.substitutions.append(('%simplifycfg', ("simplify-cfg" if int(config.llvm_ver) < 11 else "simplifycfg"))) config.substitutions.append(('%loopmssa', ("loop" if int(config.llvm_ver) < 11 else "loop-mssa"))) +config.substitutions.append(('%fc', '@Fortran_COMPILER@')) +config.substitutions.append(('%loadFortran', '-I@ENZYME_BINARY_DIR@/modules')) config.substitutions.append(('%loadBC', '' + ' @ENZYME_BINARY_DIR@/BCLoad/BCPass-' + config.llvm_ver + config.llvm_shlib_ext