From a1a5a34bdfebdcbf44c71e38843c50784f7effb6 Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Wed, 21 Jun 2023 15:20:24 +0530 Subject: [PATCH 01/13] Initial CPythonCallable implementation --- src/libasr/codegen/asr_to_c_cpp.h | 129 +++++++++++++++++++- src/libasr/pass/unused_functions.cpp | 3 +- src/lpython/semantics/python_ast_to_asr.cpp | 4 +- src/runtime/lpython/lpython.py | 63 +++++++++- 4 files changed, 192 insertions(+), 7 deletions(-) diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index eed27b53cf..049dfadaf2 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -714,13 +714,136 @@ R"(#include } sub += "\n"; src = sub; - if (f_type->m_abi == ASR::abiType::BindC - && f_type->m_deftype == ASR::deftypeType::Implementation) { - if (x.m_module_file) { + if (f_type->m_deftype == ASR::deftypeType::Implementation) { + if (f_type->m_abi == ASR::abiType::BindC && x.m_module_file) { std::string header_name = std::string(x.m_module_file); user_headers.insert(header_name); emit_headers[header_name]+= "\n" + src; src = ""; + } else if (f_type->m_abi == ASR::abiType::BindPython) { + headers.insert("Python.h"); + std::string variables_decl = "\n\n " + "// Declare arguments and return variable\n"; + std::string fill_parse_args_details = ""; + std::string fn_args = ""; + std::string fill_array_details = ""; + std::string numpy_init = ""; + + for (size_t i = 0; i < x.n_args; i++) { + ASR::Variable_t *arg = ASRUtils::EXPR2VAR(x.m_args[i]); + std::string arg_name = arg->m_name; + fill_parse_args_details += "&" + arg_name; + + if (ASR::is_a(*arg->m_type)) { + if (numpy_init.size() == 0) { + numpy_init = R"( + // Initialize NumPy + import_array();)"; + headers.insert("numpy/ndarrayobject.h"); + user_defines.insert("NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION"); + } + fn_args += "s_array_" + arg_name; + variables_decl += " PyArrayObject *" + arg_name + ";\n"; + std::string c_array_type = self().convert_variable_decl(*arg); + c_array_type = c_array_type.substr(0, + c_array_type.size() - arg_name.size() - 2); + fill_array_details += "\n\n // fill array details for " + arg_name + + "\n if (PyArray_NDIM(" + arg_name + R"() != 1) { + PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " + "Only 1 dimension array is supported for now."); + return NULL; + } + + )" + c_array_type + R"( *s_array_)" + arg_name + R"( = malloc(sizeof()" + c_array_type + R"()); + { + )" + CUtils::get_c_type_from_ttype_t(arg->m_type) + R"( *array; + // Create C arrays from numpy objects: + PyArray_Descr *descr = PyArray_DescrFromType(PyArray_TYPE()" + arg_name + R"()); + npy_intp dims[1]; + if (PyArray_AsCArray((PyObject **)&)" + arg_name + R"(, (void *)&array, dims, 1, descr) < 0) { + PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " + "Failed to create a C array"); + return NULL; + } + + s_array_)" + arg_name + R"(->data = array; + s_array_)" + arg_name + R"(->n_dims = 1; + s_array_)" + arg_name + R"(->dims[0].lower_bound = 0; + s_array_)" + arg_name + R"(->dims[0].length = dims[0]; + s_array_)" + arg_name + R"(->is_allocated = false; + } +)"; + } else { + fn_args += arg_name; + variables_decl += " " + self().convert_variable_decl(*arg) + + ";\n"; + } + if (i < x.n_args - 1) { + fill_parse_args_details += ", "; + fn_args += ", "; + } + } + + fill_parse_args_details = R"( + // Parse the arguments from Python + if (!PyArg_ParseTuple(args, "iO", )" + fill_parse_args_details + R"()) { + PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " + "Failed to parse or receive arguments from Python"); + return NULL; + })"; + + std::string fill_return_details; + if(x.m_return_var) { + variables_decl += " " + self().convert_variable_decl( + *ASRUtils::EXPR2VAR(x.m_return_var)) + ";\n"; + fill_return_details = R"( + // Build and return the result as a Python object + return Py_BuildValue("d", _lpython_return_variable);)"; + } else { + fill_return_details = R"( + // Return None + Py_RETURN_NONE;)"; + } + std::string fn_name = x.m_name; + src = sub; + src += R"(// Define the Python module and method mappings +static PyObject* )" + fn_name + R"(_define_module(PyObject* self, PyObject* args) {)" + + numpy_init + variables_decl + fill_parse_args_details + + fill_array_details + R"( + // Call the C function + _lpython_return_variable = )" + fn_name + "(" + fn_args + ");\n" + + fill_return_details + R"( +} + +// Define the module's method table +static PyMethodDef )" + fn_name + R"(_module_methods[] = { + {")" + fn_name + R"(", )" + fn_name + R"(_define_module, METH_VARARGS, + "Handle arguments & return variable and call the function"}, + {NULL, NULL, 0, NULL} +}; + +// Define the module initialization function +static struct PyModuleDef )" + fn_name + R"(_module_def = { + PyModuleDef_HEAD_INIT, + "lpython_module_)" + fn_name + R"(", + "Shared library to use LPython generated functions", + -1, + )" + fn_name + R"(_module_methods +}; + +PyMODINIT_FUNC PyInit_lpython_module_)" + fn_name + R"((void) { + PyObject* module; + + // Create the module object + module = PyModule_Create(&)" + fn_name + R"(_module_def); + if (!module) { + return NULL; + } + + return module; +} + +)"; } } current_scope = current_scope_copy; diff --git a/src/libasr/pass/unused_functions.cpp b/src/libasr/pass/unused_functions.cpp index d6c69d660e..5ab2dcc507 100644 --- a/src/libasr/pass/unused_functions.cpp +++ b/src/libasr/pass/unused_functions.cpp @@ -25,7 +25,8 @@ class CollectUnusedFunctionsVisitor : void visit_Function(const ASR::Function_t &x) { uint64_t h = get_hash((ASR::asr_t*)&x); - if (ASRUtils::get_FunctionType(x)->m_abi != ASR::abiType::BindC) { + if (ASRUtils::get_FunctionType(x)->m_abi != ASR::abiType::BindC + && ASRUtils::get_FunctionType(x)->m_abi != ASR::abiType::BindPython) { fn_declarations[h] = x.m_name; } diff --git a/src/lpython/semantics/python_ast_to_asr.cpp b/src/lpython/semantics/python_ast_to_asr.cpp index adc8792810..6e07f0f709 100644 --- a/src/lpython/semantics/python_ast_to_asr.cpp +++ b/src/lpython/semantics/python_ast_to_asr.cpp @@ -3945,9 +3945,9 @@ class SymbolTableVisitor : public CommonVisitor { current_procedure_interface = true; } else if (name == "ccallback" || name == "ccallable") { current_procedure_abi_type = ASR::abiType::BindC; - } else if (name == "pythoncall") { + } else if (name == "pythoncall" || name == "pythoncallable") { current_procedure_abi_type = ASR::abiType::BindPython; - current_procedure_interface = true; + current_procedure_interface = (name == "pythoncall"); } else if (name == "overload") { overload = true; } else if (name == "interface") { diff --git a/src/runtime/lpython/lpython.py b/src/runtime/lpython/lpython.py index c4e7895042..a76c431c32 100644 --- a/src/runtime/lpython/lpython.py +++ b/src/runtime/lpython/lpython.py @@ -837,7 +837,7 @@ def get_typenum(t): # ---------------------------------------------------------------------- # Python wrapper for the Shared library - template = f"""// Python headers + template = f"""// Python C/API headers #include // NumPy C/API headers @@ -923,3 +923,64 @@ def __call__(self, *args, **kwargs): function = getattr(__import__("lpython_module_" + self.fn_name), self.fn_name) return function(*args, **kwargs) + + +class temp_lpython: + def __init__(self, function): + def get_rtlib_dir(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(current_dir, "..") + + self.fn_name = function.__name__ + # Get the source code of the function + source_code = getsource(function) + source_code = source_code[source_code.find('\n'):] + + dir_name = '.' # "./lpython_decorator_" + self.fn_name + if not os.path.exists(dir_name): + os.mkdir(dir_name) + filename = dir_name + "/" + self.fn_name + + # Open the file for writing + with open(filename + ".py", "w") as file: + # Write the Python source code to the file + file.write("@pythoncallable") + file.write(source_code) + # ---------------------------------------------------------------------- + + r = os.system("lpython --show-c --disable-main " + + self.fn_name + ".py > " + self.fn_name + ".c") + assert r == 0, "Failed to create C file" + + gcc_flags = "" + if platform.system() == "Linux": + gcc_flags = " -shared -fPIC " + elif platform.system() == "Darwin": + gcc_flags = " -bundle -flat_namespace -undefined suppress " + else: + raise NotImplementedError("Platform not implemented") + + from numpy import get_include + from distutils.sysconfig import get_python_inc, get_python_lib, \ + get_python_version + python_path = "-I" + get_python_inc() + " " + numpy_path = "-I" + get_include() + " " + rt_path_01 = "-I" + get_rtlib_dir() + "/../libasr/runtime " + rt_path_02 = "-L" + get_rtlib_dir() + " -Wl,-rpath " \ + + get_rtlib_dir() + " -llpython_runtime " + python_lib = "-L" + get_python_lib() + "/../.. -lpython" + \ + get_python_version() + " -lm" + + + # r = os.system("cat x.c") + r = os.system("gcc -g" + gcc_flags + python_path + numpy_path + + self.fn_name + ".c -o lpython_module_" + self.fn_name + ".so " + + rt_path_01 + rt_path_02 + python_lib) + assert r == 0, "Failed to create the shared library" + + def __call__(self, *args, **kwargs): + import sys; sys.path.append('.') + # import the symbol from the shared library + function = getattr(__import__("lpython_module_" + self.fn_name), + self.fn_name) + return function(*args, **kwargs) From d3be150cf39495f19fec3649ba0b43d604c04f8e Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Wed, 21 Jun 2023 15:39:46 +0530 Subject: [PATCH 02/13] Get `type format` based on the Variable type --- src/libasr/codegen/asr_to_c_cpp.h | 39 +++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index 049dfadaf2..c9270d651b 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -602,6 +602,33 @@ R"(#include return code; } + std::string get_type_format(ASR::ttype_t *type) { + // See: https://docs.python.org/3/c-api/arg.html for more info on `type format` + switch (type->type) { + case ASR::ttypeType::Integer: { + int a_kind = ASRUtils::extract_kind_from_ttype_t(type); + if (a_kind == 4) { + return "i"; + } else { + return "l"; + } + } case ASR::ttypeType::Real : { + int a_kind = ASRUtils::extract_kind_from_ttype_t(type); + if (a_kind == 4) { + return "f"; + } else { + return "d"; + } + } case ASR::ttypeType::Logical : { + return "p"; + } case ASR::ttypeType::Array : { + return "O"; + } default: { + throw CodeGenError("CPython type format not supported yet"); + } + } + } + void visit_Function(const ASR::Function_t &x) { current_body = ""; SymbolTable* current_scope_copy = current_scope; @@ -725,6 +752,7 @@ R"(#include std::string variables_decl = "\n\n " "// Declare arguments and return variable\n"; std::string fill_parse_args_details = ""; + std::string type_format = ""; std::string fn_args = ""; std::string fill_array_details = ""; std::string numpy_init = ""; @@ -733,6 +761,7 @@ R"(#include ASR::Variable_t *arg = ASRUtils::EXPR2VAR(x.m_args[i]); std::string arg_name = arg->m_name; fill_parse_args_details += "&" + arg_name; + type_format += get_type_format(arg->m_type); if (ASR::is_a(*arg->m_type)) { if (numpy_init.size() == 0) { @@ -786,7 +815,7 @@ R"(#include fill_parse_args_details = R"( // Parse the arguments from Python - if (!PyArg_ParseTuple(args, "iO", )" + fill_parse_args_details + R"()) { + if (!PyArg_ParseTuple(args, ")" + type_format + R"(", )" + fill_parse_args_details + R"()) { PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " "Failed to parse or receive arguments from Python"); return NULL; @@ -794,11 +823,13 @@ R"(#include std::string fill_return_details; if(x.m_return_var) { - variables_decl += " " + self().convert_variable_decl( - *ASRUtils::EXPR2VAR(x.m_return_var)) + ";\n"; + ASR::Variable_t *return_var = ASRUtils::EXPR2VAR(x.m_return_var); + variables_decl += " " + self().convert_variable_decl(*return_var) + + ";\n"; fill_return_details = R"( // Build and return the result as a Python object - return Py_BuildValue("d", _lpython_return_variable);)"; + return Py_BuildValue(")" + get_type_format(return_var->m_type) + + R"(", _lpython_return_variable);)"; } else { fill_return_details = R"( // Return None From 1573c9d382470fa5b63666878ac7a6da5c3002c1 Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Wed, 21 Jun 2023 15:43:50 +0530 Subject: [PATCH 03/13] Store the generated files into a folder --- src/runtime/lpython/lpython.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/runtime/lpython/lpython.py b/src/runtime/lpython/lpython.py index a76c431c32..5252f418d5 100644 --- a/src/runtime/lpython/lpython.py +++ b/src/runtime/lpython/lpython.py @@ -936,7 +936,7 @@ def get_rtlib_dir(): source_code = getsource(function) source_code = source_code[source_code.find('\n'):] - dir_name = '.' # "./lpython_decorator_" + self.fn_name + dir_name = "./lpython_decorator_" + self.fn_name if not os.path.exists(dir_name): os.mkdir(dir_name) filename = dir_name + "/" + self.fn_name @@ -949,7 +949,7 @@ def get_rtlib_dir(): # ---------------------------------------------------------------------- r = os.system("lpython --show-c --disable-main " - + self.fn_name + ".py > " + self.fn_name + ".c") + + filename + ".py > " + filename + ".c") assert r == 0, "Failed to create C file" gcc_flags = "" @@ -972,9 +972,8 @@ def get_rtlib_dir(): get_python_version() + " -lm" - # r = os.system("cat x.c") r = os.system("gcc -g" + gcc_flags + python_path + numpy_path + - self.fn_name + ".c -o lpython_module_" + self.fn_name + ".so " + + filename + ".c -o lpython_module_" + self.fn_name + ".so " + rt_path_01 + rt_path_02 + python_lib) assert r == 0, "Failed to create the shared library" From e45c62aa5e117500444d3d5f8dddc2ad514f0f07 Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Wed, 21 Jun 2023 16:02:51 +0530 Subject: [PATCH 04/13] Simplify --- src/libasr/codegen/asr_to_c_cpp.h | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index c9270d651b..32d5ef1fcd 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -749,8 +749,7 @@ R"(#include src = ""; } else if (f_type->m_abi == ASR::abiType::BindPython) { headers.insert("Python.h"); - std::string variables_decl = "\n\n " - "// Declare arguments and return variable\n"; + std::string variables_decl = ""; std::string fill_parse_args_details = ""; std::string type_format = ""; std::string fn_args = ""; @@ -800,8 +799,7 @@ R"(#include s_array_)" + arg_name + R"(->dims[0].lower_bound = 0; s_array_)" + arg_name + R"(->dims[0].length = dims[0]; s_array_)" + arg_name + R"(->is_allocated = false; - } -)"; + })"; } else { fn_args += arg_name; variables_decl += " " + self().convert_variable_decl(*arg) @@ -813,37 +811,44 @@ R"(#include } } - fill_parse_args_details = R"( + if (fill_parse_args_details.size() > 0) { + fill_parse_args_details = R"( // Parse the arguments from Python if (!PyArg_ParseTuple(args, ")" + type_format + R"(", )" + fill_parse_args_details + R"()) { PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " "Failed to parse or receive arguments from Python"); return NULL; })"; + } + std::string fn_name = x.m_name; std::string fill_return_details; + if (variables_decl.size() > 0) { + variables_decl.insert(0, "\n\n " + "// Declare arguments and return variable\n"); + } if(x.m_return_var) { ASR::Variable_t *return_var = ASRUtils::EXPR2VAR(x.m_return_var); variables_decl += " " + self().convert_variable_decl(*return_var) + ";\n"; fill_return_details = R"( + // Call the C function + _lpython_return_variable = )" + fn_name + "(" + fn_args + ");\n" + R"( // Build and return the result as a Python object return Py_BuildValue(")" + get_type_format(return_var->m_type) + R"(", _lpython_return_variable);)"; } else { fill_return_details = R"( + // Call the C function + )" + fn_name + "(" + fn_args + ");\n" + R"( // Return None Py_RETURN_NONE;)"; } - std::string fn_name = x.m_name; src = sub; src += R"(// Define the Python module and method mappings static PyObject* )" + fn_name + R"(_define_module(PyObject* self, PyObject* args) {)" + numpy_init + variables_decl + fill_parse_args_details - + fill_array_details + R"( - // Call the C function - _lpython_return_variable = )" + fn_name + "(" + fn_args + ");\n" - + fill_return_details + R"( + + fill_array_details + fill_return_details + R"( } // Define the module's method table From c56188d00e0268dffa702ca9c6d697a041c25326 Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Wed, 21 Jun 2023 16:12:14 +0530 Subject: [PATCH 05/13] Done --- integration_tests/lpython_decorator_01.py | 4 ++-- src/libasr/codegen/asr_to_c_cpp.h | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/integration_tests/lpython_decorator_01.py b/integration_tests/lpython_decorator_01.py index eaab04b5b1..7d815e5ba9 100644 --- a/integration_tests/lpython_decorator_01.py +++ b/integration_tests/lpython_decorator_01.py @@ -1,7 +1,7 @@ from numpy import array -from lpython import i32, f64, lpython +from lpython import i32, f64, temp_lpython -@lpython +@temp_lpython def fast_sum(n: i32, x: f64[:]) -> f64: s: f64 = 0.0 i: i32 diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index 32d5ef1fcd..3538ee0039 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -766,7 +766,8 @@ R"(#include if (numpy_init.size() == 0) { numpy_init = R"( // Initialize NumPy - import_array();)"; + import_array(); +)"; headers.insert("numpy/ndarrayobject.h"); user_defines.insert("NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION"); } @@ -775,7 +776,7 @@ R"(#include std::string c_array_type = self().convert_variable_decl(*arg); c_array_type = c_array_type.substr(0, c_array_type.size() - arg_name.size() - 2); - fill_array_details += "\n\n // fill array details for " + arg_name + fill_array_details += "\n // fill array details for " + arg_name + "\n if (PyArray_NDIM(" + arg_name + R"() != 1) { PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " "Only 1 dimension array is supported for now."); @@ -799,7 +800,8 @@ R"(#include s_array_)" + arg_name + R"(->dims[0].lower_bound = 0; s_array_)" + arg_name + R"(->dims[0].length = dims[0]; s_array_)" + arg_name + R"(->is_allocated = false; - })"; + } +)"; } else { fn_args += arg_name; variables_decl += " " + self().convert_variable_decl(*arg) @@ -818,13 +820,14 @@ R"(#include PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " "Failed to parse or receive arguments from Python"); return NULL; - })"; + } +)"; } std::string fn_name = x.m_name; std::string fill_return_details; if (variables_decl.size() > 0) { - variables_decl.insert(0, "\n\n " + variables_decl.insert(0, "\n " "// Declare arguments and return variable\n"); } if(x.m_return_var) { From 7b643a3cb4cfeda275a95d77649c538afdcb565b Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Thu, 22 Jun 2023 12:30:14 +0530 Subject: [PATCH 06/13] Rename temp_lpython to lpython --- integration_tests/lpython_decorator_01.py | 4 +- src/runtime/lpython/lpython.py | 281 ---------------------- 2 files changed, 2 insertions(+), 283 deletions(-) diff --git a/integration_tests/lpython_decorator_01.py b/integration_tests/lpython_decorator_01.py index 7d815e5ba9..eaab04b5b1 100644 --- a/integration_tests/lpython_decorator_01.py +++ b/integration_tests/lpython_decorator_01.py @@ -1,7 +1,7 @@ from numpy import array -from lpython import i32, f64, temp_lpython +from lpython import i32, f64, lpython -@temp_lpython +@lpython def fast_sum(n: i32, x: f64[:]) -> f64: s: f64 = 0.0 i: i32 diff --git a/src/runtime/lpython/lpython.py b/src/runtime/lpython/lpython.py index 5252f418d5..b7c5a2e1f0 100644 --- a/src/runtime/lpython/lpython.py +++ b/src/runtime/lpython/lpython.py @@ -645,287 +645,6 @@ class lpython: to access CPython features that are not supported by LPython. """ - def __init__(self, function): - def get_rtlib_dir(): - current_dir = os.path.dirname(os.path.abspath(__file__)) - return os.path.join(current_dir, "..") - - def get_type_info(arg): - # return_type -> (`type_format`, `variable type`, `array struct name`) - # See: https://docs.python.org/3/c-api/arg.html for more info on `type_format` - # `array struct name`: used by the C backend - if arg == f64: - return ('d', "double", 'r64') - elif arg == f32: - return ('f', "float", 'r32') - elif arg == i64: - return ('l', "long int", 'i64') - elif arg == i32: - return ('i', "int", 'i32') - elif arg == bool: - return ('p', "bool", '') - elif isinstance(arg, Array): - t = get_type_info(arg._type) - if t[2] == '': - raise NotImplementedError("Type %r not implemented" % arg) - n = '' - if not isinstance(arg._dims, slice): - n = arg._dims._name - return ('O', ["PyArrayObject *", "struct "+t[2]+" *", t[1]+" *", n], '') - else: - raise NotImplementedError("Type %r not implemented" % arg) - - def get_data_type(t): - if isinstance(t, list): - return t[0] - else: - return t + " " - - def get_typenum(t): - if t == "int": - return "NPY_INT" - elif t == "long int": - return "NPY_LONG" - elif t == "float": - return "NPY_FLOAT" - elif t == "double": - return "NPY_DOUBLE" - else: - raise NotImplementedError("Type %s not implemented" % t) - - self.fn_name = function.__name__ - # Get the source code of the function - source_code = getsource(function) - source_code = source_code[source_code.find('\n'):] - - dir_name = "./lpython_decorator_" + self.fn_name - if not os.path.exists(dir_name): - os.mkdir(dir_name) - filename = dir_name + "/" + self.fn_name - - # Open the file for writing - with open(filename + ".py", "w") as file: - # Write the Python source code to the file - file.write("@ccallable") - file.write(source_code) - # ---------------------------------------------------------------------- - types = function.__annotations__ - self.arg_type_formats = "" - self.return_type = "" - self.return_type_format = "" - self.array_as_return_type = () - self.arg_types = {} - for t in types.keys(): - if t == "return": - type = get_type_info(types[t]) - if type[0] == 'O': - self.array_as_return_type = type - continue - else: - self.return_type_format = type[0] - self.return_type = type[1] - else: - type = get_type_info(types[t]) - self.arg_type_formats += type[0] - self.arg_types[t] = type[1] - # ---------------------------------------------------------------------- - # `__return_value`: used as the return variables - variables_decl = "// Declare return variables and arguments\n" - if self.return_type != "": - variables_decl += " " + get_data_type(self.return_type) \ - + "_" + self.fn_name + "_return_value;\n" - elif self.array_as_return_type: - variables_decl += " " + self.array_as_return_type[1][1] + "_" \ - + self.fn_name + "_return_value = malloc(sizeof(" \ - + self.array_as_return_type[1][1][:-2] + "));\n" - else: - variables_decl = "" - # ---------------------------------------------------------------------- - # `PyArray_AsCArray` is used to convert NumPy Arrays to C Arrays - # `fill_array_details` contains array operations to be - # performed on the arguments - # `parse_args` are used to capture the args from CPython - # `pass_args` are the args that are passed to the shared library function - fill_array_details = "" - parse_args = "" - pass_args = "" - numpy_init = "" - prefix_comma = False - for i, t in self.arg_types.items(): - if prefix_comma: - parse_args += ", " - pass_args += ", " - prefix_comma = True - if isinstance(t, list): - if numpy_init == "": - numpy_init = "// Initialize NumPy\n import_array();\n\n " - fill_array_details += f"""\n - // fill array details for {i} - if (PyArray_NDIM({i}) != 1) {{ - PyErr_SetString(PyExc_TypeError, - "Only 1 dimension is implemented for now."); - return NULL; - }} - - {t[1]}s_array_{i} = malloc(sizeof(struct r64)); - {{ - {t[2]}array; - // Create C arrays from numpy objects: - PyArray_Descr *descr = PyArray_DescrFromType(PyArray_TYPE({i})); - npy_intp dims[1]; - if (PyArray_AsCArray((PyObject **)&{i}, (void *)&array, dims, 1, descr) < 0) {{ - PyErr_SetString(PyExc_TypeError, "error converting to c array"); - return NULL; - }} - - s_array_{i}->data = array; - s_array_{i}->n_dims = 1; - s_array_{i}->dims[0].lower_bound = 0; - s_array_{i}->dims[0].length = dims[0]; - s_array_{i}->is_allocated = false; - }}""" - pass_args += "s_array_" + i - else: - pass_args += i - variables_decl += " " + get_data_type(t) + i + ";\n" - parse_args += "&" + i - - if parse_args != "": - parse_args = f"""\n // Parse the arguments from Python - if (!PyArg_ParseTuple(args, "{self.arg_type_formats}", {parse_args})) {{ - return NULL; - }}""" - - # ---------------------------------------------------------------------- - # Handle the return variable if any; otherwise, return None - fill_return_details = "" - if self.return_type != "": - fill_return_details = f"""\n\n // Call the C function - _{self.fn_name}_return_value = {self.fn_name}({pass_args}); - - // Build and return the result as a Python object - return Py_BuildValue("{self.return_type_format}", _{self.fn_name}_return_value);""" - else: - if self.array_as_return_type: - fill_return_details = f"""\n - _{self.fn_name}_return_value->data = malloc({self.array_as_return_type[1][3] - } * sizeof({self.array_as_return_type[1][2][:-2]})); - _{self.fn_name}_return_value->n_dims = 1; - _{self.fn_name}_return_value->dims[0].lower_bound = 0; - _{self.fn_name}_return_value->dims[0].length = { - self.array_as_return_type[1][3]}; - _{self.fn_name}_return_value->is_allocated = false; - - // Call the C function - {self.fn_name}({pass_args}, &_{self.fn_name}_return_value[0]); - - // Build and return the result as a Python object - {{ - npy_intp dims[] = {{{self.array_as_return_type[1][3]}}}; - PyObject* numpy_array = PyArray_SimpleNewFromData(1, dims, { - get_typenum(self.array_as_return_type[1][2][:-2])}, - _{self.fn_name}_return_value->data); - if (numpy_array == NULL) {{ - PyErr_SetString(PyExc_TypeError, "error creating an array"); - return NULL; - }} - return numpy_array; - }}""" - else: - fill_return_details = f"""{self.fn_name}({pass_args}); - Py_RETURN_NONE;""" - - # ---------------------------------------------------------------------- - # Python wrapper for the Shared library - template = f"""// Python C/API headers -#include - -// NumPy C/API headers -#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION // remove warnings -#include - -// LPython generated C code -#include "{self.fn_name}.h" - -// Define the Python module and method mappings -static PyObject* define_module(PyObject* self, PyObject* args) {{ - {numpy_init}{variables_decl}{parse_args}\ -{fill_array_details}{fill_return_details} -}} - -// Define the module's method table -static PyMethodDef module_methods[] = {{ - {{"{self.fn_name}", define_module, METH_VARARGS, - "Handle arguments & return variable and call the function"}}, - {{NULL, NULL, 0, NULL}} -}}; - -// Define the module initialization function -static struct PyModuleDef module_def = {{ - PyModuleDef_HEAD_INIT, - "lpython_module_{self.fn_name}", - "Shared library to use LPython generated functions", - -1, - module_methods -}}; - -PyMODINIT_FUNC PyInit_lpython_module_{self.fn_name}(void) {{ - PyObject* module; - - // Create the module object - module = PyModule_Create(&module_def); - if (!module) {{ - return NULL; - }} - - return module; -}} -""" - # ---------------------------------------------------------------------- - # Write the C source code to the file - with open(filename + ".c", "w") as file: - file.write(template) - - # ---------------------------------------------------------------------- - # Generate the Shared library - # TODO: Use LLVM instead of C backend - r = os.system("lpython --show-c --disable-main " - + filename + ".py > " + filename + ".h") - assert r == 0, "Failed to create C file" - - gcc_flags = "" - if platform.system() == "Linux": - gcc_flags = " -shared -fPIC " - elif platform.system() == "Darwin": - gcc_flags = " -bundle -flat_namespace -undefined suppress " - else: - raise NotImplementedError("Platform not implemented") - - from numpy import get_include - from distutils.sysconfig import get_python_inc, get_python_lib, \ - get_python_version - python_path = "-I" + get_python_inc() + " " - numpy_path = "-I" + get_include() + " " - rt_path_01 = "-I" + get_rtlib_dir() + "/../libasr/runtime " - rt_path_02 = "-L" + get_rtlib_dir() + " -Wl,-rpath " \ - + get_rtlib_dir() + " -llpython_runtime " - python_lib = "-L" + get_python_lib() + "/../.. -lpython" + \ - get_python_version() + " -lm" - - r = os.system("gcc -g" + gcc_flags + python_path + numpy_path + - filename + ".c -o lpython_module_" + self.fn_name + ".so " + - rt_path_01 + rt_path_02 + python_lib) - assert r == 0, "Failed to create the shared library" - - def __call__(self, *args, **kwargs): - import sys; sys.path.append('.') - # import the symbol from the shared library - function = getattr(__import__("lpython_module_" + self.fn_name), - self.fn_name) - return function(*args, **kwargs) - - -class temp_lpython: def __init__(self, function): def get_rtlib_dir(): current_dir = os.path.dirname(os.path.abspath(__file__)) From c47b674cb68e35c9d6a24c0cbbf5cd3e4db5dd8d Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Thu, 22 Jun 2023 12:52:16 +0530 Subject: [PATCH 07/13] Rename the function name --- src/libasr/codegen/asr_to_c_cpp.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index 3538ee0039..ff3b9e3c05 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -466,6 +466,11 @@ R"(#include if (sym_name == "exit") { sym_name = "_xx_lcompilers_changed_exit_xx"; } + ASR::FunctionType_t *f_type = ASRUtils::get_FunctionType(x); + if (f_type->m_abi == ASR::abiType::BindPython && + f_type->m_deftype == ASR::deftypeType::Implementation) { + sym_name = "_xx_internal_" + sym_name + "_xx"; + } std::string func = static_attr + inl + sub + sym_name + "("; bracket_open++; for (size_t i=0; i + ";\n"; fill_return_details = R"( // Call the C function - _lpython_return_variable = )" + fn_name + "(" + fn_args + ");\n" + R"( + _lpython_return_variable = _xx_internal_)" + fn_name + "_xx(" + fn_args + ");\n" + R"( // Build and return the result as a Python object return Py_BuildValue(")" + get_type_format(return_var->m_type) + R"(", _lpython_return_variable);)"; @@ -849,14 +854,14 @@ R"(#include } src = sub; src += R"(// Define the Python module and method mappings -static PyObject* )" + fn_name + R"(_define_module(PyObject* self, PyObject* args) {)" +static PyObject* )" + fn_name + R"((PyObject* self, PyObject* args) {)" + numpy_init + variables_decl + fill_parse_args_details + fill_array_details + fill_return_details + R"( } // Define the module's method table static PyMethodDef )" + fn_name + R"(_module_methods[] = { - {")" + fn_name + R"(", )" + fn_name + R"(_define_module, METH_VARARGS, + {")" + fn_name + R"(", )" + fn_name + R"(, METH_VARARGS, "Handle arguments & return variable and call the function"}, {NULL, NULL, 0, NULL} }; From 601bc0d8ac4379b1f406b0ea3f53828c184ed6b9 Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Thu, 22 Jun 2023 13:06:07 +0530 Subject: [PATCH 08/13] Add more details on the lpython handling --- src/libasr/codegen/asr_to_c_cpp.h | 13 ++++++++++++- src/runtime/lpython/lpython.py | 7 +++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index ff3b9e3c05..53a93cf368 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -754,7 +754,7 @@ R"(#include src = ""; } else if (f_type->m_abi == ASR::abiType::BindPython) { headers.insert("Python.h"); - std::string variables_decl = ""; + std::string variables_decl = ""; // Stores the argument declarations std::string fill_parse_args_details = ""; std::string type_format = ""; std::string fn_args = ""; @@ -773,9 +773,15 @@ R"(#include // Initialize NumPy import_array(); )"; + // Insert the headers for array handling headers.insert("numpy/ndarrayobject.h"); user_defines.insert("NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION"); } + // ------------------------------------------------------------------------- + // `PyArray_AsCArray` is used to convert NumPy Arrays to C Arrays + // `fill_array_details` contains array operations to be performed on the arguments + // `fill_parse_args_details` are used to capture the args from CPython + // `fn_args` are the arguments that are passed to the shared library function fn_args += "s_array_" + arg_name; variables_decl += " PyArrayObject *" + arg_name + ";\n"; std::string c_array_type = self().convert_variable_decl(*arg); @@ -835,6 +841,7 @@ R"(#include variables_decl.insert(0, "\n " "// Declare arguments and return variable\n"); } + // Handle the return variable if any; otherwise, return None if(x.m_return_var) { ASR::Variable_t *return_var = ASRUtils::EXPR2VAR(x.m_return_var); variables_decl += " " + self().convert_variable_decl(*return_var) @@ -852,7 +859,11 @@ R"(#include // Return None Py_RETURN_NONE;)"; } + // `sub` contains the function to be called src = sub; +// Python wrapper for the Shared library +// TODO: Instead of a function call replace it with the function body +// Basically, inlining the function by hand src += R"(// Define the Python module and method mappings static PyObject* )" + fn_name + R"((PyObject* self, PyObject* args) {)" + numpy_init + variables_decl + fill_parse_args_details diff --git a/src/runtime/lpython/lpython.py b/src/runtime/lpython/lpython.py index b7c5a2e1f0..b3fe8b94eb 100644 --- a/src/runtime/lpython/lpython.py +++ b/src/runtime/lpython/lpython.py @@ -665,8 +665,10 @@ def get_rtlib_dir(): # Write the Python source code to the file file.write("@pythoncallable") file.write(source_code) - # ---------------------------------------------------------------------- + # ---------------------------------------------------------------------- + # Generate the shared library + # TODO: Use LLVM instead of C backend r = os.system("lpython --show-c --disable-main " + filename + ".py > " + filename + ".c") assert r == 0, "Failed to create C file" @@ -690,7 +692,8 @@ def get_rtlib_dir(): python_lib = "-L" + get_python_lib() + "/../.. -lpython" + \ get_python_version() + " -lm" - + # ---------------------------------------------------------------------- + # Compile the C file and create a shared library r = os.system("gcc -g" + gcc_flags + python_path + numpy_path + filename + ".c -o lpython_module_" + self.fn_name + ".so " + rt_path_01 + rt_path_02 + python_lib) From cb4bf998fefb07dc806a8c03f84f2366c175e784 Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Thu, 22 Jun 2023 14:23:37 +0530 Subject: [PATCH 09/13] Handle array return type --- integration_tests/lpython_decorator_02.py | 2 +- src/libasr/codegen/asr_to_c_cpp.h | 60 +++++++++++++++++++---- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/integration_tests/lpython_decorator_02.py b/integration_tests/lpython_decorator_02.py index 3c64d7a640..82acf1b0d0 100644 --- a/integration_tests/lpython_decorator_02.py +++ b/integration_tests/lpython_decorator_02.py @@ -13,7 +13,7 @@ def multiply(n: i32, x: f64[:]) -> f64[n]: def test(): size: i32 = 5 x: f64[5] = array([11.0, 12.0, 13.0, 14.0, 15.0]) - y: f64[5] = (multiply(size, x)) + y: f64[5] = multiply(size, x) assert y[2] == 65. test() diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index 53a93cf368..31c88c2be4 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -760,12 +760,16 @@ R"(#include std::string fn_args = ""; std::string fill_array_details = ""; std::string numpy_init = ""; + std::string return_array_size = ""; for (size_t i = 0; i < x.n_args; i++) { ASR::Variable_t *arg = ASRUtils::EXPR2VAR(x.m_args[i]); std::string arg_name = arg->m_name; - fill_parse_args_details += "&" + arg_name; - type_format += get_type_format(arg->m_type); + if (arg_name != "_lpython_return_variable") { + if (i > 0) fill_parse_args_details += ", "; + fill_parse_args_details += "&" + arg_name; + type_format += get_type_format(arg->m_type); + } if (ASR::is_a(*arg->m_type)) { if (numpy_init.size() == 0) { @@ -782,19 +786,40 @@ R"(#include // `fill_array_details` contains array operations to be performed on the arguments // `fill_parse_args_details` are used to capture the args from CPython // `fn_args` are the arguments that are passed to the shared library function - fn_args += "s_array_" + arg_name; - variables_decl += " PyArrayObject *" + arg_name + ";\n"; std::string c_array_type = self().convert_variable_decl(*arg); c_array_type = c_array_type.substr(0, c_array_type.size() - arg_name.size() - 2); - fill_array_details += "\n // fill array details for " + arg_name + if (arg_name == "_lpython_return_variable") { + ASR::Array_t *arr = ASR::down_cast(arg->m_type); + if(arr->m_dims[0].m_length && + ASR::is_a(*arr->m_dims[0].m_length)) { + return_array_size = ASRUtils::EXPR2VAR( + arr->m_dims[0].m_length)->m_name; + } + variables_decl += " " + c_array_type + " *" + arg_name + + " = malloc(sizeof(" + c_array_type + "));\n"; + fn_args += arg_name; + fill_array_details += R"( + // Fill _lpython_return_variable + _lpython_return_variable->data = malloc(n * sizeof(double)); + _lpython_return_variable->n_dims = 1; + _lpython_return_variable->dims[0].lower_bound = 0; + _lpython_return_variable->dims[0].length = n; + _lpython_return_variable->is_allocated = false; +)"; + continue; + } else { + fn_args += "s_array_" + arg_name; + variables_decl += " PyArrayObject *" + arg_name + ";\n"; + } + fill_array_details += "\n // Fill array details for " + arg_name + "\n if (PyArray_NDIM(" + arg_name + R"() != 1) { PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " "Only 1 dimension array is supported for now."); return NULL; } - )" + c_array_type + R"( *s_array_)" + arg_name + R"( = malloc(sizeof()" + c_array_type + R"()); + )" + c_array_type + " *s_array_" + arg_name + " = malloc(sizeof(" + c_array_type + R"()); { )" + CUtils::get_c_type_from_ttype_t(arg->m_type) + R"( *array; // Create C arrays from numpy objects: @@ -819,7 +844,6 @@ R"(#include + ";\n"; } if (i < x.n_args - 1) { - fill_parse_args_details += ", "; fn_args += ", "; } } @@ -836,7 +860,7 @@ R"(#include } std::string fn_name = x.m_name; - std::string fill_return_details; + std::string fill_return_details = "\n // Call the C function"; if (variables_decl.size() > 0) { variables_decl.insert(0, "\n " "// Declare arguments and return variable\n"); @@ -847,7 +871,6 @@ R"(#include variables_decl += " " + self().convert_variable_decl(*return_var) + ";\n"; fill_return_details = R"( - // Call the C function _lpython_return_variable = _xx_internal_)" + fn_name + "_xx(" + fn_args + ");\n" + R"( // Build and return the result as a Python object return Py_BuildValue(")" + get_type_format(return_var->m_type) @@ -855,9 +878,26 @@ R"(#include } else { fill_return_details = R"( // Call the C function - )" + fn_name + "(" + fn_args + ");\n" + R"( + _xx_internal_)" + fn_name + "_xx(" + fn_args + ");\n"; + if (return_array_size.size() > 0) { + fill_return_details += R"( + // Build and return the result as a Python object + { + npy_intp dims[] = {)" + return_array_size + R"(}; + PyObject* numpy_array = PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, + _lpython_return_variable->data); + if (numpy_array == NULL) { + PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " + "Failed to create an array that was used as a return variable"); + return NULL; + } + return numpy_array; + })"; + } else { + fill_return_details += R"( // Return None Py_RETURN_NONE;)"; + } } // `sub` contains the function to be called src = sub; From a92c288ce2517c628590c749927c689f943aecca Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Thu, 22 Jun 2023 14:43:26 +0530 Subject: [PATCH 10/13] Done --- src/libasr/codegen/asr_to_c_cpp.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index 31c88c2be4..103e68985d 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -761,6 +761,7 @@ R"(#include std::string fill_array_details = ""; std::string numpy_init = ""; std::string return_array_size = ""; + std::string array_type = ""; for (size_t i = 0; i < x.n_args; i++) { ASR::Variable_t *arg = ASRUtils::EXPR2VAR(x.m_args[i]); @@ -795,6 +796,7 @@ R"(#include ASR::is_a(*arr->m_dims[0].m_length)) { return_array_size = ASRUtils::EXPR2VAR( arr->m_dims[0].m_length)->m_name; + array_type = CUtils::get_numpy_c_obj_type_conv_func_from_ttype_t(arr->m_type); } variables_decl += " " + c_array_type + " *" + arg_name + " = malloc(sizeof(" + c_array_type + "));\n"; @@ -884,7 +886,7 @@ R"(#include // Build and return the result as a Python object { npy_intp dims[] = {)" + return_array_size + R"(}; - PyObject* numpy_array = PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, + PyObject* numpy_array = PyArray_SimpleNewFromData(1, dims, )" + array_type + R"(, _lpython_return_variable->data); if (numpy_array == NULL) { PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " From 78c9aa04a9c4a5291ccfbb54156cc34683af067e Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Thu, 22 Jun 2023 14:55:00 +0530 Subject: [PATCH 11/13] Add comments --- src/libasr/codegen/asr_to_c_cpp.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index 103e68985d..bd81e22b78 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -760,8 +760,9 @@ R"(#include std::string fn_args = ""; std::string fill_array_details = ""; std::string numpy_init = ""; + // Use for return variable std::string return_array_size = ""; - std::string array_type = ""; + std::string array_type = ""; // Array type used for `PyArray_SimpleNewFromData` for (size_t i = 0; i < x.n_args; i++) { ASR::Variable_t *arg = ASRUtils::EXPR2VAR(x.m_args[i]); @@ -792,15 +793,16 @@ R"(#include c_array_type.size() - arg_name.size() - 2); if (arg_name == "_lpython_return_variable") { ASR::Array_t *arr = ASR::down_cast(arg->m_type); + fn_args += arg_name; + variables_decl += " " + c_array_type + " *" + arg_name + + " = malloc(sizeof(" + c_array_type + "));\n"; if(arr->m_dims[0].m_length && ASR::is_a(*arr->m_dims[0].m_length)) { + // name() -> f64[n]: Extract `array_type` and `n` return_array_size = ASRUtils::EXPR2VAR( arr->m_dims[0].m_length)->m_name; array_type = CUtils::get_numpy_c_obj_type_conv_func_from_ttype_t(arr->m_type); } - variables_decl += " " + c_array_type + " *" + arg_name - + " = malloc(sizeof(" + c_array_type + "));\n"; - fn_args += arg_name; fill_array_details += R"( // Fill _lpython_return_variable _lpython_return_variable->data = malloc(n * sizeof(double)); @@ -872,18 +874,17 @@ R"(#include ASR::Variable_t *return_var = ASRUtils::EXPR2VAR(x.m_return_var); variables_decl += " " + self().convert_variable_decl(*return_var) + ";\n"; - fill_return_details = R"( + fill_return_details += R"( _lpython_return_variable = _xx_internal_)" + fn_name + "_xx(" + fn_args + ");\n" + R"( // Build and return the result as a Python object return Py_BuildValue(")" + get_type_format(return_var->m_type) + R"(", _lpython_return_variable);)"; } else { - fill_return_details = R"( - // Call the C function + fill_return_details += R"( _xx_internal_)" + fn_name + "_xx(" + fn_args + ");\n"; if (return_array_size.size() > 0) { fill_return_details += R"( - // Build and return the result as a Python object + // Copy the array elements and return the result as a Python object { npy_intp dims[] = {)" + return_array_size + R"(}; PyObject* numpy_array = PyArray_SimpleNewFromData(1, dims, )" + array_type + R"(, From e7c9a9ccaaa5c6102648b73f0b71561f3229586a Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Fri, 23 Jun 2023 12:30:49 +0530 Subject: [PATCH 12/13] Handle return type --- src/libasr/codegen/asr_to_c_cpp.h | 75 +++++++++++++------------------ 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/src/libasr/codegen/asr_to_c_cpp.h b/src/libasr/codegen/asr_to_c_cpp.h index 885f53cee7..7b6f145b1a 100644 --- a/src/libasr/codegen/asr_to_c_cpp.h +++ b/src/libasr/codegen/asr_to_c_cpp.h @@ -771,6 +771,7 @@ R"(#include emit_headers[header_name]+= "\n" + src; src = ""; } else if (f_type->m_abi == ASR::abiType::BindPython) { + indentation_level += 1; headers.insert("Python.h"); std::string variables_decl = ""; // Stores the argument declarations std::string fill_parse_args_details = ""; @@ -778,19 +779,12 @@ R"(#include std::string fn_args = ""; std::string fill_array_details = ""; std::string numpy_init = ""; - // Use for return variable - std::string return_array_size = ""; - std::string array_type = ""; // Array type used for `PyArray_SimpleNewFromData` for (size_t i = 0; i < x.n_args; i++) { ASR::Variable_t *arg = ASRUtils::EXPR2VAR(x.m_args[i]); std::string arg_name = arg->m_name; - if (arg_name != "_lpython_return_variable") { - if (i > 0) fill_parse_args_details += ", "; - fill_parse_args_details += "&" + arg_name; - type_format += get_type_format(arg->m_type); - } - + fill_parse_args_details += "&" + arg_name; + type_format += get_type_format(arg->m_type); if (ASR::is_a(*arg->m_type)) { if (numpy_init.size() == 0) { numpy_init = R"( @@ -809,31 +803,9 @@ R"(#include std::string c_array_type = self().convert_variable_decl(*arg); c_array_type = c_array_type.substr(0, c_array_type.size() - arg_name.size() - 2); - if (arg_name == "_lpython_return_variable") { - ASR::Array_t *arr = ASR::down_cast(arg->m_type); - fn_args += arg_name; - variables_decl += " " + c_array_type + " *" + arg_name - + " = malloc(sizeof(" + c_array_type + "));\n"; - if(arr->m_dims[0].m_length && - ASR::is_a(*arr->m_dims[0].m_length)) { - // name() -> f64[n]: Extract `array_type` and `n` - return_array_size = ASRUtils::EXPR2VAR( - arr->m_dims[0].m_length)->m_name; - array_type = CUtils::get_numpy_c_obj_type_conv_func_from_ttype_t(arr->m_type); - } - fill_array_details += R"( - // Fill _lpython_return_variable - _lpython_return_variable->data = malloc(n * sizeof(double)); - _lpython_return_variable->n_dims = 1; - _lpython_return_variable->dims[0].lower_bound = 0; - _lpython_return_variable->dims[0].length = n; - _lpython_return_variable->is_allocated = false; -)"; - continue; - } else { - fn_args += "s_array_" + arg_name; - variables_decl += " PyArrayObject *" + arg_name + ";\n"; - } + fn_args += "s_array_" + arg_name; + variables_decl += " PyArrayObject *" + arg_name + ";\n"; + fill_array_details += "\n // Fill array details for " + arg_name + "\n if (PyArray_NDIM(" + arg_name + R"() != 1) { PyErr_SetString(PyExc_TypeError, "An error occurred in the `lpython` decorator: " @@ -866,6 +838,7 @@ R"(#include + ";\n"; } if (i < x.n_args - 1) { + fill_parse_args_details += ", "; fn_args += ", "; } } @@ -892,16 +865,18 @@ R"(#include ASR::Variable_t *return_var = ASRUtils::EXPR2VAR(x.m_return_var); variables_decl += " " + self().convert_variable_decl(*return_var) + ";\n"; - fill_return_details += R"( - _lpython_return_variable = _xx_internal_)" + fn_name + "_xx(" + fn_args + ");\n" + R"( - // Build and return the result as a Python object - return Py_BuildValue(")" + get_type_format(return_var->m_type) - + R"(", _lpython_return_variable);)"; - } else { - fill_return_details += R"( - _xx_internal_)" + fn_name + "_xx(" + fn_args + ");\n"; - if (return_array_size.size() > 0) { - fill_return_details += R"( + fill_return_details += "\n _lpython_return_variable = _xx_internal_" + + fn_name + "_xx(" + fn_args + ");\n"; + if (ASR::is_a(*return_var->m_type)) { + ASR::Array_t *arr = ASR::down_cast(return_var->m_type); + if(arr->m_dims[0].m_length && + ASR::is_a(*arr->m_dims[0].m_length)) { + // name() -> f64[n]: Extract `array_type` and `n` + std::string array_type + = CUtils::get_numpy_c_obj_type_conv_func_from_ttype_t(arr->m_type); + std::string return_array_size = ASRUtils::EXPR2VAR( + arr->m_dims[0].m_length)->m_name; + fill_return_details += R"( // Copy the array elements and return the result as a Python object { npy_intp dims[] = {)" + return_array_size + R"(}; @@ -914,11 +889,20 @@ R"(#include } return numpy_array; })"; + } else { + throw CodeGenError("Array return type without a length is not supported yet"); + } } else { fill_return_details += R"( + // Build and return the result as a Python object + return Py_BuildValue(")" + get_type_format(return_var->m_type) + + "\", _lpython_return_variable);"; + } + } else { + fill_return_details += R"( + _xx_internal_)" + fn_name + "_xx(" + fn_args + ");\n" + R"( // Return None Py_RETURN_NONE;)"; - } } // `sub` contains the function to be called src = sub; @@ -960,6 +944,7 @@ PyMODINIT_FUNC PyInit_lpython_module_)" + fn_name + R"((void) { } )"; + indentation_level -= 1; } } current_scope = current_scope_copy; From 73e24ba637e7135a2b98efe620f2c0019e36ca28 Mon Sep 17 00:00:00 2001 From: Thirumalai-Shaktivel Date: Fri, 23 Jun 2023 13:34:22 +0530 Subject: [PATCH 13/13] Add a test --- integration_tests/lpython_decorator_02.py | 29 +++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/integration_tests/lpython_decorator_02.py b/integration_tests/lpython_decorator_02.py index 82acf1b0d0..e2212cabcd 100644 --- a/integration_tests/lpython_decorator_02.py +++ b/integration_tests/lpython_decorator_02.py @@ -1,19 +1,34 @@ from numpy import array -from lpython import i32, f64, lpython, TypeVar +from lpython import i32, i64, f64, lpython, TypeVar n = TypeVar("n") @lpython -def multiply(n: i32, x: f64[:]) -> f64[n]: +def multiply_01(n: i32, x: f64[:]) -> f64[n]: i: i32 for i in range(n): x[i] *= 5.0 return x -def test(): - size: i32 = 5 - x: f64[5] = array([11.0, 12.0, 13.0, 14.0, 15.0]) - y: f64[5] = multiply(size, x) +@lpython +def multiply_02(n: i32, x: i64[:], y: i64[:]) -> i64[n]: + z: i64[n]; i: i32 + for i in range(n): + z[i] = x[i] * y[i] + return z + + +def test_01(): + size = 5 + x = array([11.0, 12.0, 13.0, 14.0, 15.0]) + y = multiply_01(size, x) assert y[2] == 65. -test() + size = 3 + x = array([11, 12, 13]) + y = array([14, 15, 16]) + z = multiply_02(size, x, y) + for i in range(size): + assert z[i] == x[i] * y[i] + +test_01()