Skip to content

Commit

Permalink
Merge pull request #228 from itpplasma/issue227_allocatable
Browse files Browse the repository at this point in the history
Fix #227: Allow allocatable derived types as function outputs
  • Loading branch information
jameskermode authored Nov 16, 2024
2 parents 79e05c3 + 4191fc2 commit cb4b362
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 3 deletions.
19 changes: 19 additions & 0 deletions examples/issue227_allocatable/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FC = gfortran
FCFLAGS = -fPIC
PYTHON = python

%.o : %.f90
${FC} ${FCFLAGS} -c $< -o $@

all: alloc_output.o
f90wrap -m itest -P alloc_output.f90 -v
f2py-f90wrap --build-dir . -c -m _itest f90wrap_alloc_output.f90 alloc_output.o

test: all
$(PYTHON) run.py

clean:
rm -f *.o f90wrap*.f90 *.so *.mod
rm -rf src.*/
rm -rf itest/
-rm -rf src.*/ .f2py_f2cmap .libs/ __pycache__/
6 changes: 6 additions & 0 deletions examples/issue227_allocatable/Makefile.meson
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include ../make.meson.inc

NAME := itest

test: build
$(PYTHON) run.py
43 changes: 43 additions & 0 deletions examples/issue227_allocatable/alloc_output.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module alloc_output
implicit none

type :: alloc_output_type
real :: a
end type alloc_output_type

contains

! This should be used by the wrapper generator
function alloc_output_type_func(val) result(out)
real, intent(in) :: val
type(alloc_output_type), allocatable :: out
allocate(out)
out%a = val
end function alloc_output_type_func


! This should be discarded by the wrapper generator
function alloc_output_intrinsic_func(val) result(out)
real, intent(in) :: val
real, allocatable :: out
allocate(out)
out = val
end function alloc_output_intrinsic_func


! This should be discarded by the wrapper generator
function alloc_output_array_func(val) result(out)
real, intent(in) :: val(:)
real, allocatable :: out(:)
allocate(out(size(val)))
out(:) = val
end function alloc_output_array_func


subroutine noalloc_output_subroutine(val, out)
real, intent(in) :: val
type(alloc_output_type), intent(inout) :: out
out%a = val
end subroutine noalloc_output_subroutine

end module alloc_output
54 changes: 54 additions & 0 deletions examples/issue227_allocatable/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python
import os
import gc
import tracemalloc

import itest


def main():
test_type_output_is_wrapped()
test_intrinsic_output_is_not_wrapped()
test_array_output_is_not_wrapped()
test_type_output_wrapper()
test_memory_leak()


def test_type_output_is_wrapped():
assert hasattr(itest.alloc_output, 'alloc_output_type_func')


def test_intrinsic_output_is_not_wrapped():
assert (not hasattr(itest.alloc_output, 'alloc_output_intrinsic_func'))


def test_array_output_is_not_wrapped():
assert (not hasattr(itest.alloc_output, 'alloc_output_array_func'))


VAL = 10.0
TOL = 1e-13


def test_type_output_wrapper():
t = itest.alloc_output.alloc_output_type_func(VAL)
assert(abs(t.a - VAL) < TOL)


def test_memory_leak():
gc.collect()
t = []
tracemalloc.start()
start_snapshot = tracemalloc.take_snapshot()
for i in range(2048):
t.append(itest.alloc_output.alloc_output_type_func(VAL))
del t
gc.collect()
end_snapshot = tracemalloc.take_snapshot()
tracemalloc.stop()
stats = end_snapshot.compare_to(start_snapshot, 'lineno')
assert sum(stat.size_diff for stat in stats) < 1024


if __name__ == '__main__':
main()
10 changes: 7 additions & 3 deletions f90wrap/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,13 @@ def visit_Procedure(self, node):
# generic_visit is done later on anyways
continue
else:
# no allocatables or pointers
if 'allocatable' in arg.attributes or 'pointer' in arg.attributes:
log.warning('removing routine %s due to allocatable/pointer arguments' % node.name)
# allocatable arguments only allowed for derived types
if 'allocatable' in arg.attributes and not arg.type.startswith('type'):
log.warning('removing routine %s due to allocatable intrinsic type arguments' % node.name)
return None
# no pointer arguments
if 'pointer' in arg.attributes:
log.warning('removing routine %s due to pointer arguments' % node.name)
return None

dims = [attrib for attrib in arg.attributes if attrib.startswith('dimension')]
Expand Down

0 comments on commit cb4b362

Please sign in to comment.