Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow homogeneous tuples as function arguments #1850

Open
wants to merge 31 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
61f7f5f
Raise correct error
EmilyBourne Apr 23, 2024
e1994d7
Add C wrapping for homogeneous tuples as arguments
EmilyBourne Apr 23, 2024
b1466d9
Strides are only needed for NumpyNDArrayType
EmilyBourne Apr 23, 2024
ca67c96
Update condition
EmilyBourne Apr 23, 2024
87ddfd7
Update conditions
EmilyBourne Apr 23, 2024
5ec1f5f
Add docstrings
EmilyBourne Apr 23, 2024
17add1a
Add bound_argument to args
EmilyBourne Apr 23, 2024
820f4e3
Unused import
EmilyBourne Apr 23, 2024
83d5a65
Add tests
EmilyBourne Apr 23, 2024
108bc28
Fix call
EmilyBourne Apr 23, 2024
6797197
Correct memory handling
EmilyBourne Apr 23, 2024
d081016
Fix Bind-C unpacking
EmilyBourne Apr 23, 2024
c65f516
Extra space in docstring
EmilyBourne Apr 24, 2024
931f3a7
Allow searching in a SyntacticTypeAnnotation
EmilyBourne May 2, 2024
674de41
Generalise used_type_names calculation
EmilyBourne May 2, 2024
86416ff
Generalise type check
EmilyBourne May 2, 2024
47a2939
Add templated test
EmilyBourne May 2, 2024
2e83a0a
Add a failing multi-level tuple test
EmilyBourne May 2, 2024
b6a0372
Raise an error for multi-level tuples
EmilyBourne May 2, 2024
2e45df9
Merge remote-tracking branch 'origin/devel' into devel-issue738
EmilyBourne May 2, 2024
738c264
Pylint f-string
EmilyBourne May 2, 2024
aad2838
Pylint errors and fix test
EmilyBourne May 2, 2024
5e5abe8
CHANGELOG
EmilyBourne May 2, 2024
a2dd1c6
Update doc
EmilyBourne May 2, 2024
7ad524b
Correct symbol search
EmilyBourne May 2, 2024
a9ca66e
Describe new _extract_X_FunctionDefArgument function and add an example
EmilyBourne May 2, 2024
5020a74
Raise error for multi-level tuples in Fortran wrapping
EmilyBourne May 2, 2024
7f9c6de
Typo
EmilyBourne May 2, 2024
d74e74b
Test works for Python
EmilyBourne May 2, 2024
323a3a8
Merge branch 'devel' into devel-issue738
EmilyBourne May 13, 2024
e4fd25a
Merge branch 'devel' into devel-issue738
EmilyBourne May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ All notable changes to this project will be documented in this file.
- #1787 : Ensure `STC` is installed with Pyccel.
- #1656 : Ensure `gFTL` is installed with Pyccel.
- #1844 : Add line numbers and code to errors from built-in function calls.
- #1867 : Add a `use_out` parameter to `pyccel.lambdify` to avoid unnecessary memory allocation.
- #1867 : Auto-generate a docstring for functions generated via calls to `pyccel.lambdify`.
- #738 : Add support for homogeneous tuples with scalar elements as arguments.
- \[INTERNALS\] Added `container_rank` property to `ast.datatypes.PyccelType` objects.
- \[DEVELOPER\] Added an improved traceback to the developer-mode errors for errors in function calls.

Expand Down
77 changes: 75 additions & 2 deletions developer_docs/wrapper_stage.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,15 +376,17 @@ PyObject* func_name(PyObject* self, PyObject* args, PyObject* kwargs);

The arguments and keyword arguments are unpacked into individual `PyObject` pointers.
Each of these objects is checked to verify the type. If the type does not match the expected type then an error is raised as described in the [C-API documentation](https://docs.python.org/3/c-api/intro.html#exceptions).
If the type does match then the value is unpacked into a C object. This is done using custom functions defined in `pyccel/stdlib/cwrapper/` or `pyccel/stdlib/cwrapper_ndarrays/` (see these files for more details).
If the type does match then the value is unpacked into a C object. This is done using custom functions defined in `pyccel/stdlib/cwrapper/` or `pyccel/stdlib/cwrapper_ndarrays/` (see these files for more details) or using functions provided by `Python.h`.

In order to create all the nodes necessary to describe the unpacking of the arguments we use functions named `_extract_X_FunctionDefArgument` where `X` is the type of the object being extracted from the `FunctionDefArgument`. This allows such functions to call each other recursively. This is notably useful for container types (tuples, lists, etc) whose elements may themselves be container types or scalar types whose type must be checked using the same functions that would be necessary were the element itself an argument.

Once C objects have been retrieved the function is called normally.

Finally all the arguments are packed into a Python tuple stored in a `PyObject` and are returned.

The wrapper is attached to the module via a `PyMethodDef` (see C-API [docs](https://docs.python.org/3/c-api/structures.html#c.PyMethodDef)).

#### Example
#### Example 1

The following Python code:
```python
Expand Down Expand Up @@ -543,6 +545,77 @@ PyObject* f_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
}
```

#### Example 2 : function with tuple arguments

The following Python code:
```python
def my_tuple_int(a : 'tuple[int,...]'):
return a[0]
```

leads to C code with the following prototype (as homogeneous tuples are treated like arrays):
```c
int64_t my_tuple_int(t_ndarray a);
```

which is then wrapped as follows:
```c
static PyObject* my_tuple_int_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
{
PyObject* a_obj;
t_ndarray a = {.shape = NULL};
int Dummy_0000;
int64_t a_size;
PyObject* Dummy_0001;
bool is_homog_tuple;
int64_t size;
int Dummy_0002;
PyObject* Dummy_0003;
int64_t Out_0001;
PyObject* Out_0001_obj;
static char *kwlist[] = {
"a",
NULL
};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &a_obj))
{
return NULL;
}
if (PyTuple_Check(a_obj))
{
size = PyTuple_Size(a_obj);
is_homog_tuple = 1;
for (Dummy_0002 = INT64_C(0); Dummy_0002 < size; Dummy_0002 += INT64_C(1))
{
Dummy_0003 = PyTuple_GetItem(a_obj, Dummy_0002);
is_homog_tuple = is_homog_tuple && PyIs_NativeInt(Dummy_0003);
}
}
else
{
is_homog_tuple = 0;
}
if (is_homog_tuple)
{
a_size = PyTuple_Size(a_obj);
a = array_create(1, (int64_t[]){a_size}, nd_int64, false, order_c);
for (Dummy_0000 = INT64_C(0); Dummy_0000 < a_size; Dummy_0000 += INT64_C(1))
{
Dummy_0001 = PyTuple_GetItem(a_obj, Dummy_0000);
GET_ELEMENT(a, nd_int64, (int64_t)Dummy_0000) = PyInt64_to_Int64(Dummy_0001);
}
}
else
{
PyErr_SetString(PyExc_TypeError, "Expected an argument of type tuple[int, ...] for argument a");
return NULL;
}
Out_0001 = my_tuple_int(a);
Out_0001_obj = Int64_to_PyLong(&Out_0001);
return Out_0001_obj;
}
```

### Interfaces

Interfaces are functions which accept more than one type.
Expand Down
2 changes: 1 addition & 1 deletion docs/type_annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ In general string type hints must be used to provide Pyccel with information abo

## Tuples

Currently tuples are supported locally in Pyccel but cannot be passed as arguments or returned. The implementation of the type annotations (as a first step to adding the missing support) is in progress. Currently homogeneous tuple type annotations are supported for local variables. Internally we handle homogeneous tuples as thought they were NumPy arrays. When creating multiple dimensional tuples it is therefore important to ensure that all objects have compatible sizes otherwise they will be handled as inhomogeneous tuples.
Currently Pyccel supports tuples used locally in functions and in certain cases as arguments, but not as returned objects or module variables. The implementation of the type annotations (including adding the missing support) is in progress. Currently homogeneous tuple type annotations are supported for local variables and function arguments (if the tuples contain scalar objects). Internally we handle homogeneous tuples as thought they were NumPy arrays. When creating multiple dimensional tuples it is therefore important to ensure that all objects have compatible sizes otherwise they will be handled as inhomogeneous tuples.

To declare a homogeneous tuple the syntax is as follows:
```python
Expand Down
16 changes: 13 additions & 3 deletions pyccel/ast/bind_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
from pyccel.ast.core import FunctionDef, ClassDef
from pyccel.ast.core import FunctionDefArgument, FunctionDefResult
from pyccel.ast.datatypes import FixedSizeType, PythonNativeInt
from pyccel.ast.numpytypes import NumpyNDArrayType
from pyccel.ast.variable import Variable
from pyccel.errors.errors import Errors
from pyccel.utilities.metaclasses import Singleton

errors = Errors()

__all__ = (
'BindCArrayVariable',
'BindCClassDef',
Expand Down Expand Up @@ -203,9 +207,15 @@ def __init__(self, var, scope, original_arg_var, wrapping_bound_argument, **kwar
shape = [scope.get_temporary_variable(PythonNativeInt(),
name=f'{name}_shape_{i+1}')
for i in range(self._rank)]
strides = [scope.get_temporary_variable(PythonNativeInt(),
name=f'{name}_stride_{i+1}')
for i in range(self._rank)]
if isinstance(original_arg_var.class_type, NumpyNDArrayType):
strides = [scope.get_temporary_variable(PythonNativeInt(),
name=f'{name}_stride_{i+1}')
for i in range(self._rank)]
else:
if original_arg_var.rank > 1:
errors.report("Wrapping multi-level tuples is not yet supported",
severity='fatal', symbol=original_arg_var)
strides = []
self._shape = shape
self._strides = strides
self._original_arg_var = original_arg_var
Expand Down
54 changes: 45 additions & 9 deletions pyccel/ast/cwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,45 +37,45 @@

errors = Errors()

__all__ = (
# --------- DATATYPES -----------
'PyccelPyObject',
'PyccelPyClassType',
'PyccelPyTypeObject',
'WrapperCustomDataType',
# --------- CLASSES -----------
'PyFunctionDef',
'PyInterface',
'PyClassDef',
'PyModule',
'PyArgKeywords',
'PyArg_ParseTupleNode',
'PyBuildValueNode',
'PyCapsule_New',
'PyCapsule_Import',
'PyGetSetDefElement',
'PyModule_Create',
'PyModule_AddObject',
'PyModInitFunc',
#--------- CONSTANTS ----------
'Py_True',
'Py_False',
'Py_None',
#----- C / PYTHON FUNCTIONS ---
'Py_INCREF',
'Py_DECREF',
'PyObject_TypeCheck',
'PySys_GetObject',
'PyUnicode_FromString',
'PyList_GetItem',
'PyList_SetItem',
'PyErr_Occurred',
'PyErr_SetString',
'PyAttributeError',
'PyNotImplementedError',
'PyTypeError',
'PyObject_TypeCheck',
)

Check warning on line 78 in pyccel/ast/cwrapper.py

View check run for this annotation

Pyccel Bot / Pyccel best practices (pyccel_lint, 3.8)

pyccel/ast/cwrapper.py#L40-L78

Sort the __all__ attribute of `cwrapper`

#-------------------------------------------------------------------
# Python DataTypes
Expand Down Expand Up @@ -970,21 +970,12 @@
arguments = [FunctionDefArgument(Variable(StringType(), name='_'))],
results = [FunctionDefResult(Variable(PyccelPyObject(), name='o', memory_handling='alias'))])

# https://docs.python.org/3/c-api/list.html#c.PyList_GetItem
PyList_GetItem = FunctionDef(name = 'PyList_GetItem',
body = [],
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name='l', memory_handling='alias')),
FunctionDefArgument(Variable(CNativeInt(), name='i'))],
results = [FunctionDefResult(Variable(PyccelPyObject(), name='o', memory_handling='alias'))])

# https://docs.python.org/3/c-api/list.html#c.PyList_SetItem
PyList_SetItem = FunctionDef(name = 'PyList_SetItem',
body = [],
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name='l', memory_handling='alias')),
FunctionDefArgument(Variable(CNativeInt(), name='i')),
FunctionDefArgument(Variable(PyccelPyObject(), name='new_item', memory_handling='alias'))],
results = [])

#-------------------------------------------------------------------

#using the documentation of PyArg_ParseTuple() and Py_BuildValue https://docs.python.org/3/c-api/arg.html
Expand Down Expand Up @@ -1083,28 +1074,73 @@
results = [FunctionDefResult(Variable(PythonNativeBool(), 'r'))],
body = [])

#-------------------------------------------------------------------
# List functions
#-------------------------------------------------------------------

# https://docs.python.org/3/c-api/list.html#c.PyList_New
PyList_New = FunctionDef(name = 'PyList_New',
arguments = [FunctionDefArgument(Variable(PythonNativeInt(), 'size'), value = LiteralInteger(0))],
results = [FunctionDefResult(Variable(PyccelPyObject(), 'r', memory_handling='alias'))],
body = [])

# https://docs.python.org/3/c-api/list.html#c.PyList_Append
PyList_Append = FunctionDef(name = 'PyList_Append',
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'list', memory_handling='alias')),
FunctionDefArgument(Variable(PyccelPyObject(), 'item', memory_handling='alias'))],
results = [FunctionDefResult(Variable(CNativeInt(), 'i'))],
body = [])

# https://docs.python.org/3/c-api/list.html#c.PyList_GetItem
PyList_GetItem = FunctionDef(name = 'PyList_GetItem',
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'list', memory_handling='alias')),
FunctionDefArgument(Variable(PythonNativeInt(), 'i'))],
results = [FunctionDefResult(Variable(PyccelPyObject(), 'item', memory_handling='alias'))],
body = [])

# https://docs.python.org/3/c-api/list.html#c.PyList_Size
PyList_Size = FunctionDef(name = 'PyList_Size',
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'list', memory_handling='alias'))],
results = [FunctionDefResult(Variable(PythonNativeInt(), 'i'))],
body = [])

# https://docs.python.org/3/c-api/list.html#c.PyList_SetItem
PyList_SetItem = FunctionDef(name = 'PyList_SetItem',
body = [],
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name='l', memory_handling='alias')),
FunctionDefArgument(Variable(CNativeInt(), name='i')),
FunctionDefArgument(Variable(PyccelPyObject(), name='new_item', memory_handling='alias'))],
results = [])

#-------------------------------------------------------------------
# Tuple functions
#-------------------------------------------------------------------

# https://docs.python.org/3/c-api/tuple.html#c.PyTuple_New
PyTuple_New = FunctionDef(name = 'PyTuple_New',
arguments = [FunctionDefArgument(Variable(PythonNativeInt(), 'size'), value = LiteralInteger(0))],
results = [FunctionDefResult(Variable(PyccelPyObject(), 'tuple', memory_handling='alias'))],
body = [])

# https://docs.python.org/3/c-api/tuple.html#c.PyTuple_Check
PyTuple_Check = FunctionDef(name = 'PyTuple_Check',
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'tuple', memory_handling='alias'))],
results = [FunctionDefResult(Variable(PythonNativeInt(), 'i'))],
body = [])

# https://docs.python.org/3/c-api/tuple.html#c.PyTuple_Size
PyTuple_Size = FunctionDef(name = 'PyTuple_Size',
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'tuple', memory_handling='alias'))],
results = [FunctionDefResult(Variable(PythonNativeInt(), 'i'))],
body = [])

# https://docs.python.org/3/c-api/tuple.html#c.PyTuple_GetItem
PyTuple_GetItem = FunctionDef(name = 'PyTuple_GetItem',
body = [],
arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name='tuple', memory_handling='alias')),
FunctionDefArgument(Variable(CNativeInt(), name='i'))],
results = [FunctionDefResult(Variable(PyccelPyObject(), name='o', memory_handling='alias'))])


# Functions definitions are defined in pyccel/stdlib/cwrapper/cwrapper.c
check_type_registry = {
Expand Down
2 changes: 1 addition & 1 deletion pyccel/ast/type_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

from .variable import DottedName, AnnotatedPyccelSymbol, IndexedElement

__all__ = (
'FunctionTypeAnnotation',
'SyntacticTypeAnnotation',
'VariableTypeAnnotation',
'UnionTypeAnnotation',
'typenames_to_dtypes',
)

Check warning on line 27 in pyccel/ast/type_annotations.py

View check run for this annotation

Pyccel Bot / Pyccel best practices (pyccel_lint, 3.8)

pyccel/ast/type_annotations.py#L21-L27

Sort the __all__ attribute of `type_annotations`

pyccel_stage = PyccelStage()

Expand Down Expand Up @@ -262,7 +262,7 @@
The order requested in the type annotation.
"""
__slots__ = ('_dtype', '_order')
_attribute_nodes = ()
_attribute_nodes = ('_dtype',)
def __init__(self, dtype, order = None):
if not isinstance(dtype, (str, DottedName, IndexedElement)):
raise ValueError("Syntactic datatypes should be strings")
Expand Down