diff --git a/flang/docs/ProcedurePointer.md b/flang/docs/ProcedurePointer.md new file mode 100644 --- /dev/null +++ b/flang/docs/ProcedurePointer.md @@ -0,0 +1,486 @@ + + +# 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>` + +A procedure pointer may have an explicit or implicit interface. T in +`!fir.ref>` 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) -> !fir.ref>) +func.func private @foo2(!fir.ref) -> !fir.ref>>) + +func.func @proc_pointer_dummy_argument(%0 : !fir.ref) -> !fir.ref>>) { + %1 = fir.load %0 : !fir.ref) -> !fir.ref>> + fir.call @foo1(%1) : ((!fir.ref) -> !fir.ref) -> () + fir.call @foo2(%0) : (!fir.ref) -> !fir.ref>>) -> () + 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) -> !fir.ref>) +func.func private @foo2(!fir.ref) -> !fir.ref>>) + +fir.global internal @ProcedurePointer : !fir.boxproc<(!fir.ref) -> !fir.ref> { + %0 = fir.zero_bits (!fir.ref) -> !fir.ref + %1 = fir.emboxproc %0 : ((!fir.ref) -> !fir.ref) -> !fir.boxproc<(!fir.ref) -> !fir.ref> + fir.has_value %1 : !fir.boxproc<(!fir.ref) -> !fir.ref> +} + +func.func @proc_pointer_global() { + %0 = fir.address_of(@ProcedurePointer) : !fir.ref) -> !fir.ref>> + %1 = fir.load %0 : !fir.ref) -> !fir.ref>> + fir.call @foo1(%1) : (!fir.boxproc<(!fir.ref) -> !fir.ref>) -> () + fir.call @foo2(%0) : (!fir.ref) -> !fir.ref>>) -> () + 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) -> !fir.ref>) +func.func private @foo2(!fir.ref) -> !fir.ref>>) + +func.func @proc_pointer_local() { + %0 = fir.alloca !fir.boxproc<(!fir.ref) -> !fir.ref> + %1 = fir.load %0 : !fir.ref) -> !fir.ref>> + %2 = fir.box_addr %1 : (!fir.boxproc<(!fir.ref) -> !fir.ref>) -> ((!fir.ref) -> !fir.ref) + %3 = fir.zero_bits (!fir.ref) -> !fir.ref + fir.store %3 to %2 : !fir.ref<(!fir.ref) -> !fir.ref> + %4 = fir.load %0 : !fir.ref) -> !fir.ref>> + fir.call @foo1(%4) : (!fir.boxproc<(!fir.ref) -> !fir.ref>) -> () + fir.call @foo2(%0) : (!fir.ref) -> !fir.ref>>) -> () + 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 + +Initially the current plan is to implement pointers to internal procedures +using the LLVM Trampoline intrinsics. This has the drawback of requiring the +stack to be executable, which is a security hole. To avoid this, we will need +improve the implementation to use heap-resident thunks. + +### 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 +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) -> !fir.ref { + %1 = fir.load %arg0 : !fir.ref + %2 = fir.convert %1 : (i32) -> f32 + return %2 : f32 +} + +func.func @Reference2Function() -> !fir.boxproc<(!fir.ref) -> !fir.ref> { + %0 = fir.alloca !fir.boxproc<(!fir.ref) -> !fir.ref> + %1 = fir.load %0 : !fir.ref) -> !fir.ref>> + return %1 : !fir.boxproc<(!fir.ref) -> !fir.ref> +} + +func.func @proc_pointer_assignment(%arg0 : !fir.ref) -> !fir.ref>>, %arg1 : !fir.ref) -> !fir.ref>>) { + %0 = fir.alloca !fir.boxproc<(!fir.ref) -> !fir.ref> {bindc_name = ".result"} + // case 1: assignment from external procedure + %1 = fir.address_of(@Procedure) : (!fir.ref) -> !fir.ref + %2 = fir.emboxproc %1 : ((!fir.ref) -> !fir.ref) -> !fir.boxproc<(!fir.ref) -> !fir.ref> + fir.store %2 to %arg0 : !fir.ref) -> !fir.ref>> + // case2: assignment from procdure pointer + %3 = fir.load %arg1 : !fir.ref) -> !fir.ref>> + fir.store %3 to %arg0 : !fir.ref) -> !fir.ref>> + // case3: assignment from a reference to a function whose result is a procedure pointer + %4 = fir.call @Reference2Function() : () -> !fir.boxproc<(!fir.ref) -> !fir.ref> + fir.store %4 to %0 : !fir.ref) -> !fir.ref>> + %5 = fir.load %0 : !fir.ref) -> !fir.ref>> + fir.store %5 to %arg0 : !fir.ref) -> !fir.ref>> + 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. + +**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.ref) -> !fir.ref, %arg1: !fir.ref) { + %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,solve:!fir.boxproc<() -> ()>}>>, !fir.field) -> !fir.ref ()>> + %3 = fir.emboxproc %arg0 : ((!fir.ref) -> !fir.ref) -> !fir.boxproc<(!fir.ref) -> !fir.ref> + %4 = fir.convert %3 : (!fir.boxproc<(!fir.ref) -> !fir.ref>) -> !fir.boxproc<() -> ()> + fir.store %4 to %2 : !fir.ref ()>> + %4 = fir.field_index solve, !fir.type<_QFtestTmatrix{element:!fir.array<2x2xf32>,solve:!fir.boxproc<() -> ()>}> + %5 = fir.coordinate_of %0, %4 : (!fir.ref,solve:!fir.boxproc<() -> ()>}>>, !fir.field) -> !fir.ref ()>> + %6 = fir.load %5 : !fir.ref ()>> + %7 = fir.convert %6 : (!fir.boxproc<() -> ()>) -> !fir.boxproc<(!fir.ref) -> !fir.ref> + %8 = fir.box_addr %7 : (!fir.boxproc<(!fir.ref) -> !fir.ref>) -> ((!fir.ref) -> !fir.ref) + %9 = fir.call %8(%arg1) : (!fir.ref) -> !fir.ref + 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/ConvertExpr.cpp:1119`: not yet implemented: procedure pointer component in derived type assignment +- `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. + +- `flang/lib/Optimizer/CodeGen/TypeConverter.h:64` TODO: BoxProcType type conversion +- `flang/lib/Optimizer/CodeGen/BoxedProcedure.cpp:136` not yet implemented: record type with a boxproc type +- fir.global for procedure pointers + +or + +- `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