Skip to content

Add implicit interfaces for function hooks in Fortran module#2809

Open
joewallwork wants to merge 27 commits into
mainfrom
2808_fortran-implicit-interfaces
Open

Add implicit interfaces for function hooks in Fortran module#2809
joewallwork wants to merge 27 commits into
mainfrom
2808_fortran-implicit-interfaces

Conversation

@joewallwork
Copy link
Copy Markdown
Collaborator

@joewallwork joewallwork commented May 7, 2026

Closes #2808.
Merges into #2807.

As mentioned in the issue, it turns out that the interfacing to the special Enzyme function hooks can be done using implicit interfaces in Fortran. That is, you can get away without providing an explicit interface block like is done in the examples so long as you declare

external :: __enzyme_autodiff

for example.

Checklist

  • Add to Fortran enzyme module
  • Use in square example

Hooking up in the allocatableArraySimple example will require fixing #2820.

@joewallwork joewallwork self-assigned this May 7, 2026
@joewallwork joewallwork added enhancement New feature or request fortran Related to Enzyme's Fortran bindings labels May 7, 2026
@joewallwork
Copy link
Copy Markdown
Collaborator Author

Huh, looks like ifx doesn't like externals with leading underscores. I hadn't tested this with that compiler yet (just flang).

@joewallwork joewallwork mentioned this pull request May 7, 2026
2 tasks
@joewallwork joewallwork force-pushed the 2808_fortran-implicit-interfaces branch from d249dc7 to 3c11497 Compare May 7, 2026 14:38
@joewallwork joewallwork changed the base branch from main to 2805_fortran-mod May 7, 2026 14:38
@joewallwork
Copy link
Copy Markdown
Collaborator Author

Huh, looks like ifx doesn't like externals with leading underscores. I hadn't tested this with that compiler yet (just flang).

Fixed in 237ebc6 by prepending with f for the Fortran versions of the function hooks.

@joewallwork joewallwork marked this pull request as ready for review May 11, 2026 16:17
@joewallwork joewallwork requested review from vchuravy and wsmoses May 11, 2026 16:18
@joewallwork joewallwork changed the title Use implicit interfaces in Fortran examples Add implicit interfaces for function hooks in Fortran module May 11, 2026
Base automatically changed from 2805_fortran-mod to main May 15, 2026 16:06
Comment thread enzyme/Fortran/enzyme.f90 Outdated
@joewallwork
Copy link
Copy Markdown
Collaborator Author

I don't understand why the square Fortran CI test is failing. I've set things up such that the enzyme.mod module file gets put in ${PROJECT_BINARY_DIR}/Fortran, which is used to define %loadFortran as an include in the Lit config. I can see the include getting picked up correctly in the CI failure but it still gives an error with opening the module file.
https://github.com/EnzymeAD/Enzyme/actions/runs/26032772064/job/76522827921?pr=2809

I don't see this error when building locally and running the test with IFX.

Comment thread enzyme/test/Fortran/CMakeLists.txt
Comment thread .github/workflows/fortran.yml Outdated
@vchuravy
Copy link
Copy Markdown
Member

vchuravy commented May 19, 2026

/home/runner/work/Enzyme/Enzyme/enzyme/test/Fortran/ReverseMode/square.f90(15): error #7013: This module file was not generated by any release of this compiler.   [ENZYME]

sigh gotta love ifx.

In the logs we also see

Building Fortran bindings
-- The Fortran compiler identification is GNU 11.4.0
-- Detecting Fortran compiler ABI info
-- Detecting Fortran compiler ABI info - done
-- Check for working Fortran compiler: /usr/bin/gfortran - skipped
-- Detecting Fortran/C Interface
-- Detecting Fortran/C Interface - Found GLOBAL and MODULE mangling
-- Verifying Fortran/C Compiler Compatibility
-- Verifying Fortran/C Compiler Compatibility - Success
Building Fortran tests

Comment thread enzyme/test/CMakeLists.txt Outdated
Comment thread .github/workflows/fortran.yml Outdated
Comment thread enzyme/Fortran/enzyme_function_hooks.f90
! 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
! RUN: if [ %llvmver -ge 13 ]; then ifx -flto -O0 -c %loadFortran %s -o /dev/stdout | %opt %loadEnzyme -enzyme -o %t && ifx -flto -O0 %t -o %t1 && %t1 | FileCheck %s; fi
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

! RUN: if [ %llvmver -ge 13 ]; then ifx -flto -O0 -c %loadFortran %s -o /dev/stdout | %opt %loadEnzyme -enzyme -o %t && ifx -flto -O0 %t -o %t1 && %t1 | FileCheck %s; fi

We should detect which fortran compiler we are using based CMAKE_FC_COMPILER_ID which should be IntelLLVM and then define a substition for %fc

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Done in fa15388.

Comment thread .github/workflows/fortran.yml Outdated
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 -DENZYME_IFX=ON
Copy link
Copy Markdown
Member

@vchuravy vchuravy May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-DCMAKE_FC_COMPILER=ifx and later we can generalize the workflow to also support flang

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b50179c.

@joewallwork
Copy link
Copy Markdown
Collaborator Author

Hm ifx still doesn't seem happy. I should have some time to debug later in the week.

@vchuravy
Copy link
Copy Markdown
Member

Currently we test Fortran integration with LLVM 15, we eventually need to update that.

I just wanted to use an Enzyme build with LLVM 21 and ran into

The `opt -passname` syntax for the new pass manager is not supported, please use `opt -passes=<pipeline>` (or the `-p` alias for a more concise version).
See https://llvm.org/docs/NewPassManager.html#invoking-opt for more details on the pass pipeline syntax.

@vchuravy
Copy link
Copy Markdown
Member

To debug the current failure:

/mnt/data/vchuravy/intel/2026.0/bin/ifx -flto -O0 -c -I/home/vchuravy/src/Enzyme/enzyme-core/build/modules /home/vchuravy/src/Enzyme/enzyme-core/enzyme/test/Fortran/ReverseMode/square.f90 -o /dev/stdout | llvm-dis
; Function Attrs: noinline nounwind optnone uwtable
define void @MAIN__() #0 {
alloca_2:
  %"$io_ctx" = alloca [8 x i64], align 8, !llfort.type_idx !5
  %"app_$DX" = alloca float, align 4, !llfort.type_idx !6
  %"app_$X" = alloca float, align 4, !llfort.type_idx !7
  %"var$1" = alloca i32, align 4, !llfort.type_idx !8
  %"(&)val$" = alloca [4 x i8], align 1, !llfort.type_idx !9
  %argblock = alloca <{ i64 }>, align 8, !llfort.type_idx !10
  %"var$2" = alloca i32, align 4, !llfort.type_idx !8
  %"(&)val$5" = alloca [4 x i8], align 1, !llfort.type_idx !11
  %argblock6 = alloca <{ i64 }>, align 8, !llfort.type_idx !10
  %func_result = call i32 @for_set_reentrancy(ptr @anon.f01f2640a9c3f724255ee7144b3336fc.0), !llfort.type_idx !8
  store float 3.000000e+00, ptr %"app_$X", align 4
  %func_result2 = call reassoc ninf nsz arcp contract afn float @math_mp_square_(ptr %"app_$X"), !llfort.type_idx !12
  store i64 0, ptr %"$io_ctx", align 8
  store [4 x i8] c"\1A\01\01\00", ptr %"(&)val$", align 1
  %BLKFIELD_float_ = getelementptr inbounds <{ i64 }>, ptr %argblock, i32 0, i32 0, !llfort.type_idx !13
  store float %func_result2, ptr %BLKFIELD_float_, align 4
  %func_result4 = call i32 (ptr, i32, i64, ptr, ptr, ...) @for_write_seq_lis(ptr %"$io_ctx", i32 -1, i64 2253038970797824, ptr %"(&)val$", ptr %argblock), !llfort.type_idx !8
  store float 0.000000e+00, ptr %"app_$DX", align 4
  call void @f__enzyme_autodiff_.t69p.t70p.t71p(ptr @math_mp_square_, ptr %"app_$X", ptr %"app_$DX"), !llfort.type_idx !14
  %"app_$DX_fetch.3" = load float, ptr %"app_$DX", align 4, !llfort.type_idx !6
  store i64 0, ptr %"$io_ctx", align 8
  store [4 x i8] c"\1A\01\01\00", ptr %"(&)val$5", align 1
  %BLKFIELD_float_7 = getelementptr inbounds <{ i64 }>, ptr %argblock6, i32 0, i32 0, !llfort.type_idx !15
  store float %"app_$DX_fetch.3", ptr %BLKFIELD_float_7, align 4
  %func_result9 = call i32 (ptr, i32, i64, ptr, ptr, ...) @for_write_seq_lis(ptr %"$io_ctx", i32 -1, i64 2253038970797824, ptr %"(&)val$5", ptr %argblock6), !llfort.type_idx !8
  ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define internal void @f__enzyme_autodiff_.t69p.t70p.t71p(ptr %arg0, ptr %arg1, ptr %arg2) #0 {
wrap_start20:
  call void (...) @f__enzyme_autodiff_(ptr %arg0, ptr %arg1, ptr %arg2)
  ret void
}

declare void @f__enzyme_autodiff_(...)

So the additional indirection is hiding things.

@vchuravy
Copy link
Copy Markdown
Member

So the issue only exists at -O0

at -O1

; Function Attrs: nounwind uwtable
define void @MAIN__() local_unnamed_addr #2 {
alloca_2:
  %"$io_ctx" = alloca [8 x i64], align 8, !llfort.type_idx !7
  %"app_$DX" = alloca float, align 4, !llfort.type_idx !8
  %"app_$X" = alloca float, align 4, !llfort.type_idx !9
  %"(&)val$" = alloca [4 x i8], align 1, !llfort.type_idx !10
  %argblock = alloca <{ i64 }>, align 8, !llfort.type_idx !11
  %"(&)val$7" = alloca [4 x i8], align 1, !llfort.type_idx !12
  %argblock8 = alloca <{ i64 }>, align 8, !llfort.type_idx !11
  %func_result = tail call i32 @for_set_fpe_(ptr nonnull @anon.f01f2640a9c3f724255ee7144b3336fc.0) #4, !llfort.type_idx !13
  %func_result2 = tail call i32 @for_set_reentrancy(ptr nonnull @anon.f01f2640a9c3f724255ee7144b3336fc.1) #4, !llfort.type_idx !13
  store float 3.000000e+00, ptr %"app_$X", align 4, !tbaa !14
  store i64 0, ptr %"$io_ctx", align 8, !tbaa !19
  %.fca.0.gep12 = getelementptr inbounds nuw [4 x i8], ptr %"(&)val$", i64 0, i64 0
  store i8 26, ptr %.fca.0.gep12, align 1, !tbaa !19
  %.fca.1.gep13 = getelementptr inbounds nuw [4 x i8], ptr %"(&)val$", i64 0, i64 1
  store i8 1, ptr %.fca.1.gep13, align 1, !tbaa !19
  %.fca.2.gep14 = getelementptr inbounds nuw [4 x i8], ptr %"(&)val$", i64 0, i64 2
  store i8 1, ptr %.fca.2.gep14, align 1, !tbaa !19
  %.fca.3.gep15 = getelementptr inbounds nuw [4 x i8], ptr %"(&)val$", i64 0, i64 3
  store i8 0, ptr %.fca.3.gep15, align 1, !tbaa !19
  %BLKFIELD_float_ = getelementptr inbounds nuw <{ i64 }>, ptr %argblock, i64 0, i32 0, !llfort.type_idx !20
  store float 9.000000e+00, ptr %BLKFIELD_float_, align 8, !tbaa !21
  %func_result6 = call i32 (ptr, i32, i64, ptr, ptr, ...) @for_write_seq_lis(ptr nonnull %"$io_ctx", i32 -1, i64 2253038970797824, ptr nonnull %"(&)val$", ptr nonnull %argblock) #4, !llfort.type_idx !13
  store float 0.000000e+00, ptr %"app_$DX", align 4, !tbaa !23
  call void (...) @f__enzyme_autodiff_(ptr nonnull @math_mp_square_, ptr nonnull %"app_$X", ptr nonnull %"app_$DX") #4
  %"app_$DX_fetch.3" = load float, ptr %"app_$DX", align 4, !tbaa !23, !llfort.type_idx !8
  store i64 0, ptr %"$io_ctx", align 8, !tbaa !19
  %.fca.0.gep = getelementptr inbounds nuw [4 x i8], ptr %"(&)val$7", i64 0, i64 0
  store i8 26, ptr %.fca.0.gep, align 1, !tbaa !19
  %.fca.1.gep = getelementptr inbounds nuw [4 x i8], ptr %"(&)val$7", i64 0, i64 1
  store i8 1, ptr %.fca.1.gep, align 1, !tbaa !19
  %.fca.2.gep = getelementptr inbounds nuw [4 x i8], ptr %"(&)val$7", i64 0, i64 2
  store i8 1, ptr %.fca.2.gep, align 1, !tbaa !19
  %.fca.3.gep = getelementptr inbounds nuw [4 x i8], ptr %"(&)val$7", i64 0, i64 3
  store i8 0, ptr %.fca.3.gep, align 1, !tbaa !19
  %BLKFIELD_float_9 = getelementptr inbounds nuw <{ i64 }>, ptr %argblock8, i64 0, i32 0, !llfort.type_idx !25
  store float %"app_$DX_fetch.3", ptr %BLKFIELD_float_9, align 8, !tbaa !26
  %func_result11 = call i32 (ptr, i32, i64, ptr, ptr, ...) @for_write_seq_lis(ptr nonnull %"$io_ctx", i32 -1, i64 2253038970797824, ptr nonnull %"(&)val$7", ptr nonnull %argblock8) #4, !llfort.type_idx !13
  ret void
}

@vchuravy
Copy link
Copy Markdown
Member

I was playing around with:

!DIR$ ATTRIBUTES FORCEINLINE :: enzyme_autodiff;
!DIR$ ATTRIBUTES FORCEINLINE :: enzyme_fwddiff;

and variations thereof, but nothing seems to stick.

Adding

    !DIR$ FORCEINLINE
    call enzyme_autodiff(square, x, dx);

does work but is kinda silly.

The options I see is:

  1. Don't test -O0 -- meh
  2. Add the callsite directive -- meh, and compiler dependent
  3. Don't do the clever indirection -- sad

@joewallwork
Copy link
Copy Markdown
Collaborator Author

I was playing around with:

!DIR$ ATTRIBUTES FORCEINLINE :: enzyme_autodiff;
!DIR$ ATTRIBUTES FORCEINLINE :: enzyme_fwddiff;

and variations thereof, but nothing seems to stick.

Adding

    !DIR$ FORCEINLINE
    call enzyme_autodiff(square, x, dx);

does work but is kinda silly.

The options I see is:

1. Don't test `-O0` -- meh

2. Add the callsite directive -- meh, and compiler dependent

3. Don't do the clever indirection -- sad

Alas. My debugging today has also not really revealed any ways forward with ifx.

I'll work on setting up a flang CI so that we can test the bindings under that compiler (works fine for me so far) and I guess we can return to ifx later.

@joewallwork joewallwork mentioned this pull request May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request fortran Related to Enzyme's Fortran bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use implicit interfaces in the Fortran examples

2 participants