Skip to content

Commit b739d46

Browse files
authored
Remove use of ndarrays when returning arrays from Fortran functions (pyccel#1968)
Remove the use of ndarray_t in the C wrapper of a function translated to Fortran returning arrays. Fixes pyccel#1967 , making it easier to remove ndarray_t later and reducing complexity. The functions `_wrap_FunctionDefResult` and `_wrap_BindCFunctionDefResult` are modified to return a dictionary with the useful information that they calculate. In addition to the body returned previously the function result arguments are additionally returned. This makes it easier to pass multiple variables to the function and simplifies `_wrap_FunctionDef` slightly. The new `PyArray` constructing function `to_pyarray` fixes the memory leak identified in pyccel#1180 . **Commit Summary** - Add an `is_alias` property to `FunctionCall` which identifies if the returned object is a pointer. - Fix a bug in `convert_to_literal` where booleans were converted to integers (because `isinstance(True, int)` returns true) - Add a new `to_pyarray` function to build a `PyArray` from a data pointer and a stack array of shapes - Use `var` to avoid the repetition of `expr.variable` in `CCodePrinter_print_Declare` - Ensure `Declare.value` is used in C code. - Allow CStackArray objects to be declared - Initialise local `void*` variables to `NULL` (fixes macosx warnings) - Create `CToPythonWrapper._call_wrapped_function` to reduce code duplication - Use variables as keys to `FunctionDef.result_pointer_map` instead of `FunctionDefResult` objects. This ensures that the map can also be used in `CToPythonWrapper` when translating to Fortran - Return a dictionary from `_wrap_FunctionDefResult` and `_wrap_BindCFunctionDefResult` to improve readability and code locality - Determine whether the returned array may need deallocating by Python (this should be the case unless the result is a pointer to an argument or is stored in a class). - Add `capsule_cleanup` function to ensure that arrays created by Pyccel and returned will be freed (fixes memory leak).
1 parent 9032a78 commit b739d46

File tree

11 files changed

+241
-107
lines changed

11 files changed

+241
-107
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ All notable changes to this project will be documented in this file.
8585
- \[INTERNALS\] Rename `_visit` functions called from a `FunctionCall` which don't match the documented naming pattern to `_build` functions.
8686
- \[INTERNALS\] Remove unnecessary argument `kind` to `Errors.set_target`.
8787
- \[INTERNALS\] Handle STC imports with Pyccel objects.
88+
- \[INTERNALS\] Stop using ndarrays as an intermediate step to return arrays from Fortran code.
8889

8990
### Deprecated
9091

pyccel/ast/core.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,6 +2055,16 @@ def interface_name(self):
20552055
"""
20562056
return self._interface_name
20572057

2058+
@property
2059+
def is_alias(self):
2060+
"""
2061+
Check if the result of the function call is an alias type.
2062+
2063+
Check if the result of the function call is an alias type.
2064+
"""
2065+
assert len(self._funcdef.results) == 1
2066+
return self._funcdef.results[0].var.is_alias
2067+
20582068
def __repr__(self):
20592069
args = ', '.join(str(a) for a in self.args)
20602070
return f'{self.func_name}({args})'

pyccel/ast/cwrapper.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from .internals import PyccelFunction
3232

33-
from .literals import LiteralString, LiteralInteger
33+
from .literals import LiteralString, LiteralInteger, Nil
3434

3535
from .variable import Variable
3636

@@ -926,7 +926,8 @@ def declarations(self):
926926
927927
Returns the declarations of the variables.
928928
"""
929-
return [Declare(v, static=(v in self._static_vars)) \
929+
return [Declare(v, static=(v in self._static_vars),
930+
value = (Nil() if isinstance(v.class_type, (VoidType, BindCPointer)) else None)) \
930931
for v in self.scope.variables.values()]
931932

932933
#-------------------------------------------------------------------

pyccel/ast/literals.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,14 +451,14 @@ def convert_to_literal(value, dtype = None):
451451

452452
# Calculate the default datatype
453453
if dtype is None:
454-
if isinstance(value, int):
454+
if isinstance(value, bool):
455+
dtype = PythonNativeBool()
456+
elif isinstance(value, int):
455457
dtype = PythonNativeInt()
456458
elif isinstance(value, float):
457459
dtype = PythonNativeFloat()
458460
elif isinstance(value, complex):
459461
dtype = PythonNativeComplex()
460-
elif isinstance(value, bool):
461-
dtype = PythonNativeBool()
462462
elif isinstance(value, str):
463463
dtype = StringType()
464464
else:

pyccel/ast/numpy_wrapper.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from .core import FunctionDef
1919
from .core import FunctionDefArgument, FunctionDefResult
2020

21-
from .c_concepts import CNativeInt
21+
from .c_concepts import CNativeInt, CStackArray
2222

2323
from .numpytypes import NumpyInt8Type, NumpyInt16Type, NumpyInt32Type, NumpyInt64Type
2424
from .numpytypes import NumpyFloat32Type, NumpyFloat64Type, NumpyFloat128Type
@@ -152,6 +152,18 @@ def get_numpy_max_acceptable_version_file():
152152
FunctionDefArgument(Variable(PyccelPyObject(), name = 'obj', memory_handling='alias'))],
153153
results = [FunctionDefResult(Variable(CNativeInt(), name = 'd'))])
154154

155+
to_pyarray = FunctionDef(name = 'to_pyarray',
156+
body = [],
157+
arguments = [FunctionDefArgument(Variable(CNativeInt(), name = 'nd')),
158+
FunctionDefArgument(Variable(CNativeInt(), name = 'typenum')),
159+
FunctionDefArgument(Variable(VoidType(), name = 'data', memory_handling='alias')),
160+
FunctionDefArgument(Variable(CStackArray(NumpyInt64Type()), 'shape')),
161+
FunctionDefArgument(Variable(PythonNativeBool(), 'c_order')),
162+
FunctionDefArgument(Variable(PythonNativeBool(), 'release_memory'))],
163+
results = [FunctionDefResult(Variable(PyccelPyObject(), name = 'arr', memory_handling='alias'))]
164+
)
165+
166+
155167
import_array = FunctionDef('import_array', (), (), ())
156168

157169
# Basic Array Flags

pyccel/codegen/printing/ccode.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
from pyccel.ast.basic import ScopedAstNode
1111

12+
from pyccel.ast.bind_c import BindCPointer
13+
1214
from pyccel.ast.builtins import PythonRange, PythonComplex
1315
from pyccel.ast.builtins import PythonPrint, PythonType
1416
from pyccel.ast.builtins import PythonList, PythonTuple, PythonSet, PythonDict
@@ -57,6 +59,7 @@
5759
from pyccel.ast.variable import InhomogeneousTupleVariable
5860

5961
from pyccel.ast.c_concepts import ObjectAddress, CMacro, CStringExpression, PointerCast, CNativeInt
62+
from pyccel.ast.c_concepts import CStackArray
6063

6164
from pyccel.codegen.printing.codeprinter import CodePrinter
6265

@@ -1342,6 +1345,8 @@ def get_declare_type(self, expr):
13421345
if isinstance(expr.class_type, (HomogeneousSetType, HomogeneousListType, DictType)):
13431346
dtype = self.get_c_type(expr.class_type)
13441347
return dtype
1348+
if isinstance(expr.class_type, CStackArray):
1349+
return self.get_c_type(expr.class_type.element_type)
13451350
if isinstance(expr.class_type,(HomogeneousTupleType, NumpyNDArrayType)):
13461351
if expr.rank > 15:
13471352
errors.report(UNSUPPORTED_ARRAY_RANK, symbol=expr, severity='fatal')
@@ -1379,20 +1384,31 @@ def _print_FuncAddressDeclare(self, expr):
13791384
return f'{ret_type} (*{name})({arg_code});\n'
13801385

13811386
def _print_Declare(self, expr):
1382-
if isinstance(expr.variable, InhomogeneousTupleVariable):
1383-
return ''.join(self._print_Declare(Declare(v,intent=expr.intent, static=expr.static)) for v in expr.variable)
1387+
var = expr.variable
1388+
if isinstance(var, InhomogeneousTupleVariable):
1389+
return ''.join(self._print_Declare(Declare(v,intent=expr.intent, static=expr.static)) for v in var)
1390+
1391+
declaration_type = self.get_declare_type(var)
1392+
variable = self._print(var.name)
13841393

1385-
declaration_type = self.get_declare_type(expr.variable)
1386-
variable = self._print(expr.variable.name)
1394+
init = f' = {self._print(expr.value)}' if expr.value is not None else ''
13871395

1388-
if expr.variable.is_stack_array:
1389-
preface, init = self._init_stack_array(expr.variable,)
1396+
if isinstance(var.class_type, CStackArray):
1397+
assert init == ''
1398+
preface = ''
1399+
if isinstance(var.alloc_shape[0], (int, LiteralInteger)):
1400+
init = f'[{var.alloc_shape[0]}]'
1401+
else:
1402+
declaration_type += '*'
1403+
init = ''
1404+
elif var.is_stack_array:
1405+
preface, init = self._init_stack_array(var,)
13901406
elif declaration_type == 't_ndarray' and not self._in_header:
1407+
assert init == ''
13911408
preface = ''
13921409
init = ' = {.shape = NULL}'
13931410
else:
13941411
preface = ''
1395-
init = ''
13961412

13971413
external = 'extern ' if expr.external else ''
13981414
static = 'static ' if expr.static else ''
@@ -2023,7 +2039,8 @@ def _print_FunctionDef(self, expr):
20232039
self._additional_args.append(results)
20242040

20252041
body = self._print(expr.body)
2026-
decs = [Declare(i) if isinstance(i, Variable) else FuncAddressDeclare(i) for i in expr.local_vars]
2042+
decs = [Declare(i, value=(Nil() if i.is_alias and isinstance(i.class_type, (VoidType, BindCPointer)) else None))
2043+
if isinstance(i, Variable) else FuncAddressDeclare(i) for i in expr.local_vars]
20272044

20282045
if len(results) == 1 :
20292046
res = results[0]

pyccel/codegen/printing/cwrappercode.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,14 +501,16 @@ def _print_Declare(self, expr):
501501

502502
variable = self._print(expr.variable.name)
503503

504+
init = f' = {self._print(expr.value)}' if expr.value is not None else ''
505+
504506
if var.rank == 0:
505-
return f'{static}{external}{declaration_type} {variable};\n'
507+
return f'{static}{external}{declaration_type} {variable}{init};\n'
506508

507509
size = var.shape[0]
508510
if isinstance(size, LiteralInteger):
509511
return f'{static}{external}{declaration_type} {variable}[{size}];\n'
510512
else:
511-
return f'{static}{external}{declaration_type}* {variable};\n'
513+
return f'{static}{external}{declaration_type}* {variable}{init};\n'
512514
else:
513515
return CCodePrinter._print_Declare(self, expr)
514516

0 commit comments

Comments
 (0)