feb9d33a2a
This supports the codegen for global procedure pointer in BoxedProcedure pass. Reset the boxproc type. Reviewed By: jeanPerier Differential Revision: https://reviews.llvm.org/D138273
487 lines
16 KiB
Markdown
487 lines
16 KiB
Markdown
<!--===- 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
|
|
|
|
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<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/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.
|
|
|
|
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
|