EnzymeAD / EnzymeAD/Enzyme

Add support for IFX RTL Memory Management Functions

Open
#1,155 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
LLVM
Stars
1.7k
Forks
188
Avg merge
2d 4h
Merged PRs (30d)
22

Description

# Add Support for IFX RTL Memory Management Functions

I'm looking to add support for the following IFX runtime library memory management, that are present in [MOM6](https://github.com/mom-ocean/MOM6):

```llvm
i32 @for_alloc_allocatable_handle(i64, i8** nocapture , i32 , i8* )
i32 @for_alloc_allocatable(i64 , i8** nocapture , i32 )
i32 @for_allocate_handle(i64 , i8** nocapture , i32 , i8* )
i32 @for_allocate(i64 , i8** nocapture , i32 )
i32 @for_dealloc_allocatable_handle(i8* nocapture readonly , i32 , i8* )
i32 @for_dealloc_allocatable(i8* nocapture readonly , i32 )
i32 @for_deallocate(i8* nocapture readonly , i32 )
i8* @for_realloc_lhs(i8* nocapture , i8* nocapture readonly , i32 )
i8* @for_array_copy_in(i8* nocapture readonly )
void @for_array_copy_out(i8* nocapture , i8* nocapture )
i32 @for_alloc_assign_v2(i8* nocapture , i8* nocapture , i8* , i8* nocapture , i32 )
i32 @for_alloc_copy(i8* nocapture readonly , i8* nocapture readonly , i8* , i8* , i32 )
i32 @for_dealloc_all_nocheck(i8* nocapture , i8* nocapture , i32 )
i32 @for_dealloc_all_nopdtlen(i8* nocapture , i8* nocapture , i32 )
i32 @for_deallocate_all(i8* nocapture , i8* nocapture , i32 )
```

Below I have provided some initial details about the functions, but I will continue to update these details.

#### IFX Functions

---
```llvm
i32 @for_alloc_allocatable_handle(i64 , i8** nocapture , i32 , i8* )
i32 @for_alloc_allocatable(i64 , i8** nocapture , i32 )
i32 @for_allocate_handle(i64 , i8** nocapture , i32 , i8* )
i32 @for_allocate(i64 , i8** nocapture , i32 )
```
These functions are for allocating arrays and objects:
- `for_alloc_allocatable_handle`: for allocatable arrays `type(Obj), allocatable :: objectArray(:)`
- `for_alloc_allocatable`: for allocatable objects `type(Obj), allocatable :: object`
- `for_allocate_handle`: for array pointers `type(Obj), pointer :: objectArrayPtr(:)`
- `for_allocate`: for object pointers `type(Obj), pointer :: objectPtr`

The first argument is the size of the allocation in bytes, the second is the ptr at which to store the allocation, and the third is a bitflag. The first and third function, also have a fourth argument that is reserved for coarray support, but this isn't currently supported in IFX (and coarrays aren't used in MOM6). Any nested allocatable (or pointer) objects/arrays are left unallocated. Also the returned integer is sometimes used.

---
```llvm
i32 @for_dealloc_allocatable_handle(i8* nocapture readonly , i32 , i8* )
i32 @for_dealloc_allocatable(i8* nocapture readonly , i32 )
i32 @for_deallocate(i8* nocapture readonly , i32 )
```
The first argument is the pointer to deallocate, and the second is a bitflag. The first function also has a third argument that is reserved for coarray support. These functions don't deallocate nested allocatable (or pointer) objects/arrays, and again the returned integer is sometimes used.

The function `for_dealloc_allocatable_handle` can be used as the counterpart `for_alloc_allocatable_handle` and `for_allocate_handle` when one is deallocating objects without nested allocatabes. While the function `for_dealloc_allocatable` can be the counterpart of both `for_alloc_allocatable` and `for_allocate` under same conditions.

The flag argument of `for_dealloc_allocatable_handle`, and `for_dealloc_allocatable` seems to have 2 main ways of being constructed, and the choice between them seems to be based on whether the deallocation corresponds to an explicit deallocation i.e. `deallocate(...)` in the source code, or an implicit deallocation i.e. cleaning up local allocatable variables at the end of a function.

In MOM6 `for_deallocate` is rare, and it only seems to be used for deallocating some variables marked `intent(out)` on entry to the function. Also I've only see the number 262144 used as the flag argument for this function.

```fortran
! ___Fortran___

type :: Point
integer :: x, y
end type
type(Point), allocatable :: objectArray(:)
type(Point), pointer :: objectArrayPtr(:)
type(Point), allocatable :: object
type(Point), pointer :: objectPtr
...
allocate(objectArray(length)) ! for_alloc_allocatable_handle
allocate(objectArrayPtr(length)) ! for_allocate_handle
allocate(object) ! for_alloc_allocatable
allocate(objectPtr) ! for_allocate
...
deallocate(objectArray) ! for_dealloc_allocatable_handle
deallocate(objectArrayPtr) ! for_dealloc_allocatable_handle
deallocate(object) ! for_dealloc_allocatable
deallocate(objectPtr) ! for_dealloc_allocatable
```

---
```llvm
i8* @for_realloc_lhs(i8* nocapture , i8* nocapture readonly , i32 )
```
This function is used for reallocating the LHS of an assignment to an allocatable array, to the same shape and size as the RHS.

The first argument is a pointer to an allocatable array (on the LHS of an assignment) that is to be reallocated to the same shape/size as array pointed to by the second argument. The flag seems to be constructed in the same way every time, and I have not seen an example where the return pointer is used. Also it is common to create temporary local variables for use as the second argument.

```fortran
! ___Fortran___

real, allocatable :: fpArray1(:)
real, allocatable :: fpArray2(:)
...
fpArray1 = fpArray2 ! for_realloc_lhs
```

---
```llvm
i8* @for_array_copy_in(i8* nocapture readonly )
void @for_array_copy_out(i8* nocapture , i8* nocapture )
```
The function `for_array_copy_in` returns a pointer to a block of memory that is a copy of the memory held by the first argument. While the function `for_array_copy_out` copys the memory held by the first argument, in the second argument.

---
```llvm
i32 @for_alloc_assign_v2(i8* nocapture , i8* nocapture , i8* , i8* nocapture , i32 )
i32 @for_alloc_copy(i8* nocapture readonly , i8* nocapture readonly , i8* , i8* , i32 )
```
The `for_alloc_assign_v2` is used for both allocation, and assignment when the LHS of an assignment is an object of nested allocatables. While at the moment I'm not 100% sure about the the function `for_alloc_copy`, but I think it copies both the first and second argument, in to the third and fourth arguments respectively (though I have only seen examples where the fourth and fifth arguments are null, and 0 respectively).

---
```llvm
i32 @for_dealloc_all_nocheck(i8* nocapture , i8* nocapture , i32 )
i32 @for_dealloc_all_nopdtlen(i8* nocapture , i8* nocapture , i32 )
i32 @for_deallocate_all(i8* nocapture , i8* nocapture , i32 )
```
These deallocation functions are used for deallocating nested allocatables all at once.

The first argument is a pointer to an object that describes the nesting of allocatable objects in the second argument. The second argument is the pointer to deallocate, and the third argument is a flag.

```fortran
! ___Fortran___

type :: arrayContainer
integer(kind=8) :: dummy
real, allocatable :: array(:)
end type
...
type(arrayContainer) :: v
type(arrayContainer) :: v_copy
...
allocate(v%array(size))
...
v_copy = v ! for_alloc_assign_v2
...
! for_dealloc_all_nocheck (at the end of the function)
```

#### Remarks
- Procedure arguments that are marked with the attribute, `intent(out)`, are deallocated on entry to a function if they are allocated.
```fortran
! ___Fortran___

subroutine foo(x_in, x_out)
real, allocatable, intent(in) :: x_in(:)
real, allocatable, intent(out) :: x_out(:) ! deallocated on entry
...
end subroutine
```
- All local allocatable variables are deallocated at the end of a procedure if they have been allocated.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.