Skip to content

Add implicit interfaces for function hooks in Fortran module#2809

Merged
joewallwork merged 33 commits into
mainfrom
2808_fortran-implicit-interfaces
Jun 12, 2026
Merged

Add implicit interfaces for function hooks in Fortran module#2809
joewallwork merged 33 commits into
mainfrom
2808_fortran-implicit-interfaces

Conversation

@joewallwork

@joewallwork joewallwork commented May 7, 2026

Copy link
Copy Markdown
Collaborator

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/test/Fortran/ReverseMode/square.f90 Outdated
Comment thread .github/workflows/fortran.yml Outdated
@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
@vchuravy

Copy link
Copy Markdown
Member

I think the conclusion from Monday was to go with f__enzyme_autodiff or add something like ENZYME_autodiff.

@joewallwork

Copy link
Copy Markdown
Collaborator Author

I think the conclusion from Monday was to go with f__enzyme_autodiff or add something like ENZYME_autodiff.

Reverted in 57855fa.

@vchuravy

Copy link
Copy Markdown
Member
+ ifx -flto -O0 -c -I/home/runner/work/Enzyme/Enzyme/build/modules /home/runner/work/Enzyme/Enzyme/enzyme/test/Fortran/ReverseMode/square.f90 -o /dev/stdout
+ /usr/lib/llvm-15/bin/opt --enable-new-pm=0 -load=/home/runner/work/Enzyme/Enzyme/build/Enzyme/LLVMEnzyme-15.so --enzyme-attributor=0 -enzyme -o /home/runner/work/Enzyme/Enzyme/build/test/Fortran/ReverseMode/Output/square.f90.tmp
error: <unknown>:0:0: in function f__enzyme_autodiff_.t72p.t73p.t74p void (float (float*)*, float*, float*): Enzyme: failed to find fn to differentiate  call void (...) @f__enzyme_autodiff_(float (float*)* %arg0, float* %arg1, float* %arg2) - found - float (float*)* %arg0

So ifx still doesn't like this at O0

@joewallwork

Copy link
Copy Markdown
Collaborator Author

So ifx still doesn't like this at O0

Alas, indeed. I just pushed c4fac16 to the other PR #2837 to check that ifx works for all the tests there so long as -O0 is not used.

@joewallwork

Copy link
Copy Markdown
Collaborator Author

Alas, indeed. I just pushed c4fac16 to the other PR #2837 to check that ifx works for all the tests there so long as -O0 is not used.

Okay that passes. @vchuravy How about we revert back to the neater enzyme_autodiff pattern and just don't test implicit interfaces under ifx with -O0 for now? I can add a documentation page that says you need to use an explicit interface at -O0 with ifx. Would that be an acceptable resolution?

@joewallwork

Copy link
Copy Markdown
Collaborator Author

Alas, indeed. I just pushed c4fac16 to the other PR #2837 to check that ifx works for all the tests there so long as -O0 is not used.

Okay that passes. @vchuravy How about we revert back to the neater enzyme_autodiff pattern and just don't test implicit interfaces under ifx with -O0 for now? I can add a documentation page that says you need to use an explicit interface at -O0 with ifx. Would that be an acceptable resolution?

Pushed this proposal in recent commits. Eventually we want the docs to live on the webpage but a README should be sufficient at this stage.

@joewallwork joewallwork enabled auto-merge June 12, 2026 07:55
@joewallwork joewallwork added this pull request to the merge queue Jun 12, 2026
Merged via the queue into main with commit c965083 Jun 12, 2026
30 checks passed
@joewallwork joewallwork deleted the 2808_fortran-implicit-interfaces branch June 12, 2026 08:32
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