Repository navigation
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 sThe 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.
Activity
czgdp1807 commentedon Jun 28, 2022
So, here's what I understand from our discussions and the issue above,
@cpython_callable- Makes the function callable from CPython. For that you will be required to create a CPython extension module which can be imported from CPython code.@cpython- Makes CPython functions callable from LPython executables. For this you will be required to usePyObject_*functions. For example,PyObject_CallFunctionyou can easily call any CPython function from an executable.Please let me know if I interpreted your thoughts correctly.
certik commentedon Jun 28, 2022
Yes. When you say "you will be required", it means LPython will do it for you automatically. The user will not be required to do that.
czgdp1807 commentedon Jun 28, 2022
Yeah. By "you will be required" I mean we will be required to add it in LPython so that it can create a CPython extension for the user.
Smit-create commentedon Jul 28, 2022
In my understanding, for
cpython_callable:Is that correct? I guess,
numbaalso does something similar.certik commentedon Jul 28, 2022
I think so. I think
@cpython_callableshould compile it to a regular binary function, and then create a Python wrapper by creating a Python extension module that calls this function.certik commentedon Mar 21, 2023
This should be called
@lpython.jit, here is how to use it from CPython:This will call LPython under the hood, give it the source code of the function, LPython compiles via LLVM into a shared library, and it also emits Python wrappers of this function, all into the same shared library. Then the decorator loads this shared library and calls this function, so when you do later in CPython:
It will call into the function implemented in the shared library.
To get started, we can just generate C code (or even Cython) for the Python wrappers part, and compile everything in. Later we can maybe represent such C code using ASR itself.
So here are a list of steps:
jitdecorator tolpython.pythat does the following:fast_sumas a string into a filefrom our_shared_library import fast_math_cfast_math_cas the result of the decoratorOnce this works, we will then make some of these steps more robust, such as 4. should be later done by LPython instead of the decorator, and 5. should also be done by LPython with the appropriate option. Also 2. should be done by calling LPython directly from CPython, not going via a file.
harshsingh-24 commentedon Apr 12, 2023
How are these decorators like
@ccallor@jitimplemented in LPython? I am unaware of the files and places where things need to be added in order to make it work this way. Any sample PR so that I learn about their implementations in LPython? @certik @Thirumalai-ShaktivelThirumalai-Shaktivel commentedon Apr 12, 2023
ccallis implemented here:lpython/src/runtime/lpython/lpython.py
Line 246 in ae6c547
A simple code like this would work:
jitis not implemented yet.harshsingh-24 commentedon Apr 13, 2023
I was trying to make a decorator on my system first. It covers the step 1 and 2 as mentioned by @certik. Am I on the right track here @Thirumalai-Shaktivel ? It basically creates a new file called
fast_sum.pywhich saves the definition of the function.Thirumalai-Shaktivel commentedon Apr 13, 2023
I think so.
harshsingh-24 commentedon Apr 13, 2023
How should I register
jitin LPython? If I directly make a class in LPython as shown above, it does not work. @Thirumalai-Shaktivel . I think it is something related toast_to_asr.cpp.Thirumalai-Shaktivel commentedon May 5, 2023
Useful links:
https://docs.python.org/3/c-api/arg.html
https://docs.python.org/3/c-api/intro.html#include-files
https://docs.python.org/3/extending/extending.html#back-to-the-example
https://numpy.org/devdocs/reference/c-api/array.html
certik commentedon May 9, 2023
For
@cpython, all we need for MVP is this:So we need:
The decorator should be called
@cpython, consistent with the@lpythondecorator.Smit-create commentedon May 24, 2023
For
@cpython, we can use the following steps:cpythondecorator and emit an extension module using Python-C API inC.BindCin LLVM.Is this the way?
certik commentedon May 24, 2023
We can start with:
Then the next step is:
certik commentedon May 24, 2023
Python allows the following:
certik commentedon May 24, 2023
One idea is to take this:
and this gets converted to the following CPython function (you can even imagine it being in a file):
And then we just call it, by first converting all arguments and then calling the function
fusing Python API (PyObject_CallFunction) and then we convert the return value back. We have to somehow evaluate "f", or maybe we can create a separatea.pyfile and use that.certik commentedon May 24, 2023
I think we should design first an equivalent of
@ccall. An example of a@ccall:So we should also have:
Now we import the "mymodule.py" (from the path provided by
-Itolpython) and call the functionrunfrom it, and convert arguments.We'll have to add
BindPython. And we add the "module" to Function.Once we implement physical types, this is part of the physical type.
certik commentedon May 24, 2023
Another idea is to do this at import time:
But somehow specify that
mymoduleis a CPython module.certik commentedon May 24, 2023
In the same way we should add
@pythoncallablewhich wraps an LPython function into CPython. It can then likely be used from the@lpythondecorator as well (it would generate LPython code that uses the@pythoncallabledecorator). Mostly at the ASR level, the function with implementation (body) can have anABI=BindPython. This can makes array to be of physical type NumPy arrays. Then our optimizer can cast a NumPy array to a descriptor array with no array data movement, thus ensuring good performance.certik commentedon May 30, 2023
Current status:
@lpythonand@pythoncallis implemented as a prototype@cpythoncallworking in LLVM@pythoncallableworking in both LLVM and C backends, that creates a function that can be called from Python; the@lpythondecorator can then use it.ubaidsk commentedon Jun 9, 2023
@certik it seems that to support
@ pythoncallin thellvmbackend, there are two approaches possible.Which approach shall we follow?
certik commentedon Jun 9, 2023
I would call the Python C/API directly from LLVM. If something cannot be done easily, then we can write a simple wrapper in our C runtime library for just that one thing. If (and only if) the Python C/API is called from LLVM, we also need to link with the Python shared library.
ubaidsk commentedon Jun 9, 2023
Got it. Thank you!
Thirumalai-Shaktivel commentedon Jun 12, 2023
Apart from this, which other array handling should we need support?
certik commentedon Jun 12, 2023
I think these are the main use cases.
certik commentedon Jun 12, 2023
This can't work:
You have to use:
Thirumalai-Shaktivel commentedon Jun 19, 2023
What can be the difference between
@lpythonand@pythoncallable?My understanding is:
@lpythonis compiled using CPython only. We store the function into a file, compile it using LPython and create a shared library. Later call the required symbol from the shared library.@pythoncallableis compiled using LPython? Compile and create a shared library?Later using
@lpythondecorator import the function?Thirumalai-Shaktivel commentedon Jun 22, 2023
The following throws error message:
Error:
./lpython_decorator_test/test.pyThe problem is that we must insert an import statement:
from numpy import sqrtin the lpython file.Thirumalai-Shaktivel commentedon Jun 22, 2023
TODO for
lpython