Description
We need to design some kind of a decorator similar to ccall
and ccallable
(which interfaces to and from C) but to/from Python. Since our surface language is Python, the design can be different. Such as:
@cpython
def f(n: i32, x: str) -> str:
import sympy
x_ = sympy.Symbol(x)
e = ((x_+5)**n).expand()
return str(e)
LPython will take the contents of the decorated function and turn it into a series of Python C/API calls in ASR, and it will convert the arguments to / from ASR's i32/str into CPython correctly.
This approach will allow calling any Python code naturally from LPython. With @ccall
we can naturally call any C code.
The decorator can be just called @python
, but since LPython is Python (subset at first, but we are keeping the door open to possibly support all of Python later), it might be confusing what exactly is meant by it.
The second decorator we need is to call a function from Python. Something like:
@cpython_callable
def fast_sum(x: f64[n]) -> f64:
s: f64 = 0
for i in range(n):
s += x[i]
return s
The contents of the function is regular LPython code that will get optimized by our optimizers to produce top performing (vectorized) machine code. But the interface will get exposed to CPython, so LPython will do the conversion from a NumPy array into f64[n]
, and it will convert the return value f64
from ASR to CPython. Not sure about all the details yet in ASR, but with the C backend it will produce code that when compiled would create a CPython extension module (shared library) that you just import in Python and it will just work.
With these two decorartors (@cpython
and cpython_callable
) we can use LPython to create extension modules to CPython. We can easily call any C library if we want to (thus from this perspective it is very similar to Cython), and we can also just implement things in regular LPython code which gets highly optimized. Everything is just Python, so you can take any such LPython code and if you run it with CPython, it will work (just slower).
This is a very powerful design that allows to 100% stay in Python. One starts with CPython, implements some code, then takes parts of it, extract into a separate module that LPython can compile, decorate the API with @cpython_callable
and use LPython to compile. Since @ccall
works from both CPython and LPython, one can interface any C code easily from both. It's a very simple gradual approach, and it allows to achieve the top performance, as we deliver more on LPython as a regular production optimizing compiler, that is very good with arrays.