| <!--===- docs/ProcedurePointer.md | |
| 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 | |
| --> | |
| # Procedure Pointer | |
| A procedure pointer is a procedure that has the EXTERNAL and POINTER attributes. | |
| This document summarizes what of context the procedure pointers should appear, | |
| and how they are lowered to FIR. | |
| The current plan is to use/extend the `BoxedProcedure` pass for the conversion | |
| to LLVM IR, and thus will not be lowering the procedure-pointer-related | |
| operations to LLVM IR in `CodeGen.cpp`. | |
| ## Fortran standard | |
| Here is a list of the sections and constraints of the Fortran standard involved | |
| for procedure pointers. | |
| - 8.5.4 Components | |
| - C757 | |
| - C758 | |
| - C759 | |
| - 8.5.9: EXTERNAL attribute | |
| - 8.5.14: POINTER attribute | |
| - C853 | |
| - A procedure pointer shall not be referenced unless it is pointer associated | |
| with a target procedure. | |
| - 8.5.15 PROTECTED attribute | |
| - C855 | |
| - 8.5.16 SAVE attribute | |
| - (4) A procedure pointer declared in the scoping unit of a main program, | |
| module, or submodule implicitly has the SAVE attribute. | |
| - 8.10.2.1 COMMON statement | |
| - C8119 | |
| - 10.2.2.2 Pointer assignment statement | |
| - C1028 | |
| - C1029 | |
| - 10.2.2.4 Procedure pointer assignment | |
| - 11.1.3 ASSOCIATE construct | |
| - C1005 | |
| - 12.6.3 Data transfer input/output list | |
| - C1233 | |
| - 15.2.2.4 Procedure pointers | |
| - A procedure pointer may be pointer associated with an external procedure, an | |
| internal procedure, an intrinsic procedure, a module procedure, or a dummy | |
| procedure that is not a procedure pointer. | |
| - 15.4.3.6 Procedure declaration statement | |
| - 15.5.2.9(5) Actual arguments associated with dummy procedure entities | |
| - 16.9.16 ASSOCIATED(POINTER [, TARGET]) | |
| - POINTER may be a procedure pointer, and TARGET may be proc-target in a | |
| pointer assignment statement (10.2.2). | |
| - 16.9.144 NULL([MOLD]) | |
| - MOLD may be a procedure pointer. | |
| - 18.2.3.4 C_F_PROCPOINTER(CPTR, FPTR) | |
| - FPTR shall be a procedure pointer, and not be a component of a coindexed | |
| object. | |
| - C.1.1 A procedure that is not a procedure pointer can be an actual argument | |
| that corresponds to a procedure pointer dummy argument with the INTENT(IN) | |
| attribute. | |
| --- | |
| ## Representation in FIR | |
| ### Procedure pointer `!fir.ref<!fir.boxproc<T>>` | |
| A procedure pointer may have an explicit or implicit interface. T in | |
| `!fir.ref<!fir.boxproc<T>>` is the function type, which is `() -> ()` if the | |
| procedure pointer has the implicit interface declared as | |
| `procedure(), pointer :: p`. | |
| A procedure declaration statement specifies EXTERNAL attribute (8.5.9) for all | |
| entities for all entities in the procedure declaration list. | |
| ### Actual arguments associated with dummy procedure entities | |
| The actual argument may be a procedure pointer, a valid target for the dummy | |
| pointer, a reference to the NULL() intrinsic, or a reference to a function that | |
| returns a procedure pointer. | |
| If the interface is explicit, and the dummy argument is procedure pointer, the | |
| reference is resolved as the pointer to the procedure; otherwise, the reference | |
| is resolved as the pointer target. | |
| **Fortran case 1** | |
| ```fortran | |
| subroutine proc_pointer_dummy_argument(p) | |
| interface | |
| function func(x) | |
| integer :: x | |
| end function func | |
| end interface | |
| procedure(func), pointer :: p | |
| call foo1(p) | |
| call foo2(p) | |
| contains | |
| subroutine foo2(q) | |
| interface | |
| function func(x) | |
| integer :: x | |
| end function func | |
| end interface | |
| procedure(func), pointer :: q | |
| end subroutine foo2 | |
| end subroutine proc_pointer_dummy_argument | |
| ``` | |
| **FIR for case 1** | |
| ``` | |
| func.func private @foo1(!fir.boxproc<(!fir.ref<i32>) -> f32>) | |
| func.func private @foo2(!fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>) | |
| func.func @proc_pointer_dummy_argument(%0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>) { | |
| %1 = fir.load %0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| fir.call @foo1(%1) : (!fir.boxproc<(!fir.ref<i32>) -> f32>) -> () | |
| fir.call @foo2(%0) : (!fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>) -> () | |
| return | |
| } | |
| ``` | |
| **Fortran case 2** | |
| ```fortran | |
| subroutine proc_pointer_global() | |
| interface | |
| function func(x) | |
| integer :: x | |
| end function func | |
| end interface | |
| procedure(func), pointer, save :: p | |
| call foo1(p) | |
| call foo2(p) | |
| contains | |
| subroutine foo2(q) | |
| interface | |
| function func(x) | |
| integer :: x | |
| end function func | |
| end interface | |
| procedure(func), pointer :: q | |
| end subroutine foo2 | |
| end subroutine proc_pointer_global | |
| ``` | |
| **FIR for case 2** | |
| ``` | |
| func.func private @foo1(!fir.boxproc<(!fir.ref<i32>) -> f32>) | |
| func.func private @foo2(!fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>) | |
| fir.global internal @ProcedurePointer : !fir.boxproc<(!fir.ref<i32>) -> f32> { | |
| %0 = fir.zero_bits (!fir.ref<i32>) -> f32 | |
| %1 = fir.emboxproc %0 : ((!fir.ref<i32>) -> f32) -> !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| fir.has_value %1 : !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| } | |
| func.func @proc_pointer_global() { | |
| %0 = fir.address_of(@ProcedurePointer) : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| %1 = fir.load %0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| fir.call @foo1(%1) : (!fir.boxproc<(!fir.ref<i32>) -> f32>) -> () | |
| fir.call @foo2(%0) : (!fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>) -> () | |
| return | |
| } | |
| ``` | |
| **Fortran case 3** | |
| ```fortran | |
| subroutine proc_pointer_local() | |
| interface | |
| function func(x) | |
| integer :: x | |
| end function func | |
| end interface | |
| procedure(func), pointer :: p | |
| call foo1(p) | |
| call foo2(p) | |
| contains | |
| subroutine foo2(q) | |
| interface | |
| function func(x) | |
| integer :: x | |
| end function func | |
| end interface | |
| procedure(func), pointer :: q | |
| end subroutine foo2 | |
| end subroutine proc_pointer_local | |
| ``` | |
| **FIR for case 3** | |
| ``` | |
| func.func private @foo1(!fir.boxproc<(!fir.ref<i32>) -> f32>) | |
| func.func private @foo2(!fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>) | |
| func.func @proc_pointer_local() { | |
| %0 = fir.alloca !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| %1 = fir.zero_bits (!fir.ref<i32>) -> f32 | |
| %2 = fir.emboxproc %1 : ((!fir.ref<i32>) -> f32) -> !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| fir.store %2 to %0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| %4 = fir.load %0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| fir.call @foo1(%4) : (!fir.boxproc<(!fir.ref<i32>) -> f32>) -> () | |
| fir.call @foo2(%0) : (!fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>) -> () | |
| return | |
| } | |
| ``` | |
| It is possible to pass procedure pointers to a C function. If the C function has | |
| an explicit interface in fortran code, and the dummy argument is a procedure | |
| pointer, the code passes a pointer to the procedure as the actual argument | |
| (see Case 5); Otherwise, the code passes the procedure pointer target as the | |
| actual argument (see Case 4). | |
| **Case 4** | |
| ```c | |
| void func_(void (*foo)(int *)) { | |
| int *x, y = 1; | |
| x = &y; | |
| foo(x); | |
| } | |
| ``` | |
| ```fortran | |
| program main | |
| procedure(), pointer :: pp | |
| pp=>print_x | |
| call func(pp) | |
| contains | |
| subroutine print_x(x) | |
| integer :: x | |
| print *, x | |
| end | |
| end | |
| ``` | |
| Note that the internal procedure is not one good usage, but it works in | |
| implementation. It is better to use BIND(C) external or module procedure as | |
| right-hand side proc-target. | |
| **Case 5** | |
| ```c | |
| void func_(void (**foo)(int *)) { | |
| int *x, y = 1; | |
| x = &y; | |
| (*foo)(x); | |
| } | |
| ``` | |
| ```fortran | |
| program main | |
| interface | |
| subroutine func(p) | |
| procedure(), pointer :: p | |
| end | |
| end interface | |
| procedure(), pointer :: pp | |
| pp=>print_x | |
| call func(pp) | |
| contains | |
| subroutine print_x(x) | |
| integer :: x | |
| print *, x | |
| end | |
| end | |
| ``` | |
| Case 4 and Case 5 are not recommended from Fortran 2003 standard, which provides | |
| the feature of interoperability with C to handle this. Specifically, | |
| C_F_PROCPOINTER is used to associate a procedure pointer with the target of a C | |
| function pointer. C_FUNPTR is also designed for interoperability with any C | |
| function pointer type. | |
| ### Procedure pointer to function returning a character type | |
| The dummy procedure pointer may not have a function type with an assumed length | |
| due to C721 and C723. | |
| ### Procedure pointer to internal procedure | |
| By default, Flang implements pointers to internal procedures using the LLVM | |
| trampoline intrinsics. This requires the stack to be executable. On x86-64 and | |
| AArch64 targets, the opt-in `-fsafe-trampoline` mode instead uses a | |
| runtime-managed trampoline pool that does not require an executable stack. See | |
| the [trampoline design documentation](InternalProcedureTrampolines.md) for details. | |
| ### Procedure pointer assignment `p => proc` | |
| The right-hand side may be a procedure, a procedure pointer, or a function whose | |
| result is a procedure pointer. | |
| The procedure could be a BIND(C) procedure. The lowering of it is the same as | |
| that of an external or module procedure. The case of internal procedure has been | |
| discussed above. | |
| ```c | |
| #include<stdio.h> | |
| void func_(int *x) { | |
| printf("%d\n", *x); | |
| } | |
| ``` | |
| ```fortran | |
| program main | |
| interface | |
| subroutine func(x) bind(C) | |
| integer :: x | |
| end | |
| end interface | |
| procedure(func), bind(C, name="func_") :: proc | |
| procedure(func), pointer :: pp | |
| integer :: x = 5 | |
| pp=>proc | |
| call pp(x) | |
| end | |
| ``` | |
| **Fortran case** | |
| ```fortran | |
| subroutine proc_pointer_assignment(arg0, arg1) | |
| interface | |
| function func(x) | |
| integer :: x | |
| end | |
| end interface | |
| procedure(func), pointer :: arg0, arg1 | |
| real, external, bind(C, name="Procedure") :: proc | |
| arg0=>proc ! case 1 | |
| arg0=>arg1 ! case 2 | |
| arg0=>reffunc ! case 3 | |
| contains | |
| function reffunc() result(pp) | |
| interface | |
| function func(x) | |
| integer :: x | |
| end | |
| end interface | |
| procedure(func), pointer :: pp | |
| end | |
| end | |
| function proc(x) bind(C, name="Procedure") | |
| integer :: x | |
| proc = real(x) | |
| end | |
| ``` | |
| **FIR** | |
| ``` | |
| func.func @Procedure(%arg0 : !fir.ref<i32>) -> f32 { | |
| %0 = fir.alloca f32 {bindc_name = "res", uniq_name = "_QFfuncEres"} | |
| %1 = fir.load %arg0 : !fir.ref<i32> | |
| %2 = fir.convert %1 : (i32) -> f32 | |
| fir.store %2 to %0 : !fir.ref<f32> | |
| %3 = fir.load %0 : !fir.ref<f32> | |
| return %3 : f32 | |
| } | |
| func.func @Reference2Function() -> !fir.boxproc<(!fir.ref<i32>) -> f32> { | |
| %0 = fir.alloca !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| %1 = fir.load %0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| return %1 : !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| } | |
| func.func @proc_pointer_assignment(%arg0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>, %arg1 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>>) { | |
| %0 = fir.alloca !fir.boxproc<(!fir.ref<i32>) -> f32> {bindc_name = ".result"} | |
| // case 1: assignment from external procedure | |
| %1 = fir.address_of(@Procedure) : (!fir.ref<i32>) -> f32 | |
| %2 = fir.emboxproc %1 : ((!fir.ref<i32>) -> f32) -> !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| fir.store %2 to %arg0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| // case2: assignment from procdure pointer | |
| %3 = fir.load %arg1 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| fir.store %3 to %arg0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| // case3: assignment from a reference to a function whose result is a procedure pointer | |
| %4 = fir.call @Reference2Function() : () -> !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| fir.store %4 to %0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| %5 = fir.load %0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| fir.store %5 to %arg0 : !fir.ref<!fir.boxproc<(!fir.ref<i32>) -> f32>> | |
| return | |
| } | |
| ``` | |
| ### Procedure pointer components | |
| Having procedure pointers in derived types permits `methods` to be dynamically | |
| bound to objects. Such procedure pointer components will have the type | |
| !fir.boxproc<T>. | |
| **Fortran** | |
| ```fortran | |
| subroutine proc_pointer_component(a, i, f) | |
| interface | |
| function func(x) | |
| integer :: x | |
| end | |
| end interface | |
| type matrix | |
| real :: element(2,2) | |
| procedure(func), pointer, nopass :: solve | |
| end type | |
| integer :: i | |
| procedure(func) :: f | |
| type(matrix) :: a | |
| a%solve=>f | |
| r = a%solve(i) | |
| end subroutine proc_pointer_component | |
| ``` | |
| **FIR** | |
| ``` | |
| func.func @proc_pointer_component(%arg0 : !fir.boxproc<(!fir.ref<i32>) -> f32>, %arg1: !fir.ref<i32>) { | |
| %0 = fir.alloca !fir.type<_QFtestTmatrix{element:!fir.array<2x2xf32>,solve:!fir.boxproc<() -> ()>}> | |
| %1 = fir.field_index solve, !fir.type<_QFtestTmatrix{element:!fir.array<2x2xf32>,solve:!fir.boxproc<() -> ()>}> | |
| %2 = fir.coordinate_of %0, %1 : (!fir.ref<!fir.type<_QFtestTmatrix{element:!fir.array<2x2xf32>,solve:!fir.boxproc<() -> ()>}>>, !fir.field) -> !fir.ref<!fir.boxproc<() -> ()>> | |
| %3 = fir.convert %arg0 : (!fir.boxproc<(!fir.ref<i32>) -> f32>) -> !fir.boxproc<() -> ()> | |
| fir.store %3 to %2 : !fir.ref<!fir.boxproc<() -> ()>> | |
| %4 = fir.field_index solve, !fir.type<_QFtestTmatrix{element:!fir.array<2x2xf32>,solve:!fir.boxproc<() -> ()>}> | |
| %5 = fir.coordinate_of %0, %4 : (!fir.ref<!fir.type<_QFtestTmatrix{element:!fir.array<2x2xf32>,solve:!fir.boxproc<() -> ()>}>>, !fir.field) -> !fir.ref<!fir.boxproc<() -> ()>> | |
| %6 = fir.load %5 : !fir.ref<!fir.boxproc<() -> ()>> | |
| %7 = fir.convert %6 : (!fir.boxproc<() -> ()>) -> !fir.boxproc<(!fir.ref<i32>) -> f32> | |
| %8 = fir.box_addr %7 : (!fir.boxproc<(!fir.ref<i32>) -> f32>) -> ((!fir.ref<i32>) -> f32) | |
| %9 = fir.call %8(%arg1) : (!fir.ref<i32>) -> f32 | |
| return | |
| } | |
| ``` | |
| --- | |
| ## Testing | |
| The lowering part is tested with LIT tests in tree, but the execution tests are | |
| useful for full testing. | |
| LLVM IR testing is also helpful with the initial check. A C function pointer is | |
| semantically equivalent to a Fortran procedure in LLVM IR level, and a pointer | |
| to a C function pointer is semantically equivalent to a Fortran procedure | |
| pointer in LLVM IR level. That is, a Fortran procedure will be converted to a | |
| opaque pointer in LLVM IR level, which is the same for a C function pointer; | |
| a Fortran procedure pointer will be converted to a opaque pointer pointing to | |
| a opaque pointer, which is the same for a pointer to a C function pointer. | |
| The tests should include the following | |
| - function result, subroutine/function arguments with varying types | |
| - non-character scalar | |
| - character (assumed-length and non-assumed-length) | |
| - array (static and dynamic) | |
| - character array | |
| - derived type | |
| - ... (polymorphic?) | |
| - internal/external/module procedure or a C function as the target | |
| - procedure pointer initialization | |
| - procedure pointer assignment | |
| - procedure pointer, procedure pointer target passed to a C function | |
| - procedure pointer, procedure pointer target passed to a Fortran procedure | |
| - procedure pointer component in derived types | |
| --- | |
| ## Current TODOs | |
| Current list of TODOs in lowering: | |
| - `flang/lib/Lower/CallInterface.cpp:708`: not yet implemented: procedure pointer result not yet handled | |
| - `flang/lib/Lower/CallInterface.cpp:961`: not yet implemented: procedure pointer arguments | |
| - `flang/lib/Lower/CallInterface.cpp:993`: not yet implemented: procedure pointer results | |
| - `flang/lib/Lower/ConvertType.cpp:228`: not yet implemented: procedure pointers | |
| - `flang/lib/Lower/Bridge.cpp:2438`: not yet implemented: procedure pointer assignment | |
| - `flang/lib/Lower/ConvertVariable.cpp:348`: not yet implemented: procedure pointer component default initialization | |
| - `flang/lib/Lower/ConvertVariable.cpp:416`: not yet implemented: procedure pointer globals | |
| - `flang/lib/Lower/ConvertVariable.cpp:1459`: not yet implemented: procedure pointers | |
| - `flang/lib/Lower/HostAssociations.cpp:162`: not yet implemented: capture procedure pointer in internal procedure | |
| - lowering of procedure pointers in ASSOCIATED, NULL, and C_F_PROCPOINTER | |
| Current list of TODOs in code generation: | |
| NOTE: There are any number of possible implementations. | |
| BoxedProcedure pass | |
| or | |
| - `flang/lib/Optimizer/CodeGen/TypeConverter.h:64` TODO: BoxProcType type conversion | |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:2080` not yet implemented: fir.emboxproc codegen | |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:629` not yet implemented: fir.boxproc_host codegen | |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:1078` not yet implemented: fir.len_param_index codegen | |
| - `flang/lib/Optimizer/CodeGen/CodeGen.cpp:3166` not yet implemented: fir.unboxproc codegen | |
| --- | |
| Resources: | |
| - [1] Fortran standard |