Skip to content

Commit 38cd377

Browse files
authored
Merge pull request #1931 from Shaikh-Ubaid/pythoncall_arrays_c
Pythoncall: Support arrays of int, real as args
2 parents e792868 + 4f13699 commit 38cd377

File tree

10 files changed

+278
-15
lines changed

10 files changed

+278
-15
lines changed

integration_tests/CMakeLists.txt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
1+
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
22

33
project(lpython_tests C)
44

@@ -37,14 +37,21 @@ set_property(TARGET lpython_rtlib PROPERTY INTERFACE_LINK_LIBRARIES
3737
${LPYTHON_RTLIB_LIBRARY})
3838
target_link_libraries(lpython_rtlib INTERFACE m)
3939

40-
find_package(Python COMPONENTS Development)
40+
find_package(Python COMPONENTS Interpreter Development)
41+
execute_process(
42+
COMMAND "${Python_EXECUTABLE}"
43+
-c "import numpy; print(numpy.get_include())"
44+
OUTPUT_VARIABLE NUMPY_INCLUDE_DIR
45+
OUTPUT_STRIP_TRAILING_WHITESPACE
46+
)
4147
message("\n")
4248
message("System has the Python development artifacts: ${Python_Development_FOUND}")
4349
message("The Python include directories: ${Python_INCLUDE_DIRS}")
4450
message("The Python libraries: ${Python_LIBRARIES}")
4551
message("The Python library directories: ${Python_LIBRARY_DIRS}")
4652
message("The Python runtime library directories: ${Python_RUNTIME_LIBRARY_DIRS}")
4753
message("Python version: ${Python_VERSION}")
54+
message("Numpy Include Directory: ${NUMPY_INCLUDE_DIR}")
4855

4956
enable_testing()
5057

@@ -125,6 +132,7 @@ macro(RUN_UTIL RUN_FAIL RUN_NAME RUN_FILE_NAME RUN_IMPORT_PATH RUN_LABELS RUN_EN
125132
set_target_properties(${name} PROPERTIES LINKER_LANGUAGE C)
126133
target_link_libraries(${name} lpython_rtlib)
127134
if (run_enable_cpython)
135+
target_include_directories(${name} PRIVATE ${NUMPY_INCLUDE_DIR})
128136
target_link_libraries(${name} Python::Python)
129137
if (extra_files)
130138
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/${extra_files} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
@@ -235,7 +243,7 @@ macro(RUN_UTIL RUN_FAIL RUN_NAME RUN_FILE_NAME RUN_IMPORT_PATH RUN_LABELS RUN_EN
235243
endmacro(RUN_UTIL)
236244

237245
macro(RUN)
238-
set(options FAIL NOFAST ENABLE_CPYTHON)
246+
set(options FAIL NOFAST ENABLE_CPYTHON ENABLE_CNUMPY)
239247
set(oneValueArgs NAME IMPORT_PATH)
240248
set(multiValueArgs LABELS EXTRAFILES)
241249
cmake_parse_arguments(RUN "${options}" "${oneValueArgs}"
@@ -248,6 +256,10 @@ macro(RUN)
248256
set(RUN_EXTRA_ARGS ${RUN_EXTRA_ARGS} --enable-cpython)
249257
endif()
250258

259+
if (RUN_ENABLE_CNUMPY)
260+
set(RUN_EXTRA_ARGS ${RUN_EXTRA_ARGS} --enable-cnumpy)
261+
endif()
262+
251263
RUN_UTIL(RUN_FAIL RUN_NAME RUN_FILE_NAME RUN_IMPORT_PATH RUN_LABELS RUN_ENABLE_CPYTHON RUN_EXTRAFILES RUN_EXTRA_ARGS)
252264

253265
if ((NOT DISABLE_FAST) AND (NOT RUN_NOFAST))
@@ -504,6 +516,7 @@ RUN(NAME bindc_05 LABELS llvm c
504516
RUN(NAME bindc_06 LABELS llvm c
505517
EXTRAFILES bindc_06b.c)
506518
RUN(NAME bindpy_01 LABELS cpython c ENABLE_CPYTHON NOFAST EXTRAFILES bindpy_01_module.py)
519+
RUN(NAME bindpy_02 LABELS cpython c ENABLE_CPYTHON ENABLE_CNUMPY EXTRAFILES bindpy_02_module.py)
507520
RUN(NAME test_generics_01 LABELS cpython llvm c NOFAST)
508521
RUN(NAME test_cmath LABELS cpython llvm c NOFAST)
509522
RUN(NAME test_complex_01 LABELS cpython llvm c wasm wasm_x64)

integration_tests/bindpy_02.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from lpython import i32, f64, pythoncall, Const
2+
from numpy import empty
3+
4+
@pythoncall(module = "bindpy_02_module")
5+
def get_cpython_version() -> str:
6+
pass
7+
8+
@pythoncall(module = "bindpy_02_module")
9+
def get_int_array_sum(a: i32[:]) -> i32:
10+
pass
11+
12+
@pythoncall(module = "bindpy_02_module")
13+
def get_int_array_product(a: i32[:]) -> i32:
14+
pass
15+
16+
@pythoncall(module = "bindpy_02_module")
17+
def get_float_array_sum(a: f64[:]) -> f64:
18+
pass
19+
20+
@pythoncall(module = "bindpy_02_module")
21+
def get_float_array_product(a: f64[:]) -> f64:
22+
pass
23+
24+
@pythoncall(module = "bindpy_02_module")
25+
def show_array_dot_product(a: i32[:], b: f64[:]):
26+
pass
27+
28+
# Integers:
29+
def test_array_ints():
30+
n: Const[i32] = 5
31+
a: i32[n] = empty([n], dtype=int)
32+
33+
i: i32
34+
for i in range(n):
35+
a[i] = i + 10
36+
37+
assert get_int_array_sum(a) == i32(60)
38+
assert get_int_array_product(a) == i32(240240)
39+
40+
# Floats
41+
def test_array_floats():
42+
n: Const[i32] = 3
43+
m: Const[i32] = 5
44+
b: f64[n, m] = empty([n, m], dtype=float)
45+
46+
i: i32
47+
j: i32
48+
49+
for i in range(n):
50+
for j in range(m):
51+
b[i, j] = f64((i + 1) * (j + 1))
52+
53+
assert abs(get_float_array_sum(b) - (90.000000)) <= 1e-4
54+
assert abs(get_float_array_product(b) - (13436928000.000000)) <= 1e-4
55+
56+
def test_array_broadcast():
57+
n: Const[i32] = 3
58+
m: Const[i32] = 5
59+
a: i32[n] = empty([n], dtype=int)
60+
b: f64[n, m] = empty([n, m], dtype=float)
61+
62+
i: i32
63+
j: i32
64+
for i in range(n):
65+
a[i] = i + 10
66+
67+
for i in range(n):
68+
for j in range(m):
69+
b[i, j] = f64((i + 1) * (j + 1))
70+
71+
show_array_dot_product(a, b)
72+
73+
def main0():
74+
print("CPython version: ", get_cpython_version())
75+
76+
test_array_ints()
77+
test_array_floats()
78+
test_array_broadcast()
79+
80+
main0()

integration_tests/bindpy_02_module.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import numpy as np
2+
3+
def get_cpython_version():
4+
import platform
5+
return platform.python_version()
6+
7+
def get_int_array_sum(a):
8+
return np.sum(a)
9+
10+
def get_int_array_product(a):
11+
return np.prod(a)
12+
13+
def get_float_array_sum(a):
14+
return np.sum(a)
15+
16+
def get_float_array_product(a):
17+
return np.prod(a)
18+
19+
def show_array_dot_product(a, b):
20+
print(a, b)
21+
print(a @ b)

src/bin/lpython.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,9 @@ int link_executable(const std::vector<std::string> &infiles,
12491249
if (compiler_options.enable_cpython) {
12501250
std::string py_version = "3.10";
12511251
std::string py_flags = R"(-I $CONDA_PREFIX/include/python)" + py_version + R"( -L$CONDA_PREFIX/lib -Wl,-rpath -Wl,$CONDA_PREFIX/lib -lpython)" + py_version + R"()";
1252+
if (compiler_options.enable_cnumpy) {
1253+
py_flags += R"( -I$CONDA_PREFIX/lib/python)" + py_version + R"(/site-packages/numpy/core/include)";
1254+
}
12521255
cmd += " " + py_flags;
12531256
}
12541257
int err = system(cmd.c_str());
@@ -1562,6 +1565,7 @@ int main(int argc, char *argv[])
15621565
app.add_flag("--get-rtl-dir", print_rtl_dir, "Print the path to the runtime library file");
15631566
app.add_flag("--verbose", compiler_options.verbose, "Print debugging statements");
15641567
app.add_flag("--enable-cpython", compiler_options.enable_cpython, "Enable CPython runtime");
1568+
app.add_flag("--enable-cnumpy", compiler_options.enable_cnumpy, "Enable C-Numpy runtime");
15651569

15661570
// LSP specific options
15671571
app.add_flag("--show-errors", show_errors, "Show errors when LSP is running in the background");

src/libasr/asr_utils.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3363,8 +3363,9 @@ static inline bool is_pass_array_by_data_possible(ASR::Function_t* x, std::vecto
33633363
// BindC interfaces already pass array by data pointer so we don't need to track
33643364
// them and use extra variables for their dimensional information. Only those functions
33653365
// need to be tracked which by default pass arrays by using descriptors.
3366-
if (ASRUtils::get_FunctionType(x)->m_abi == ASR::abiType::BindC &&
3367-
ASRUtils::get_FunctionType(x)->m_deftype == ASR::deftypeType::Interface) {
3366+
if ((ASRUtils::get_FunctionType(x)->m_abi == ASR::abiType::BindC
3367+
|| ASRUtils::get_FunctionType(x)->m_abi == ASR::abiType::BindPython)
3368+
&& ASRUtils::get_FunctionType(x)->m_deftype == ASR::deftypeType::Interface) {
33683369
return false;
33693370
}
33703371

src/libasr/codegen/asr_to_c.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,8 @@ class ASRToCVisitor : public BaseCCPPVisitor<ASRToCVisitor>
670670
c_utils_functions->set_indentation(indentation_level, indentation_spaces);
671671
c_utils_functions->set_global_scope(global_scope);
672672
c_ds_api->set_c_utils_functions(c_utils_functions.get());
673-
673+
bind_py_utils_functions->set_indentation(indentation_level, indentation_spaces);
674+
bind_py_utils_functions->set_global_scope(global_scope);
674675
std::string head =
675676
R"(
676677
#include <stdlib.h>
@@ -783,6 +784,9 @@ R"(
783784
}
784785
}
785786
std::string to_include = "";
787+
for (auto &s: user_defines) {
788+
to_include += "#define " + s + "\n";
789+
}
786790
for (auto &s: headers) {
787791
to_include += "#include <" + s + ">\n";
788792
}
@@ -803,6 +807,12 @@ R"(
803807
if( c_utils_functions->get_generated_code().size() > 0 ) {
804808
util_funcs_defined = "\n" + c_utils_functions->get_generated_code() + "\n";
805809
}
810+
if( bind_py_utils_functions->get_util_func_decls().size() > 0 ) {
811+
array_types_decls += "\n" + bind_py_utils_functions->get_util_func_decls() + "\n";
812+
}
813+
if( bind_py_utils_functions->get_generated_code().size() > 0 ) {
814+
util_funcs_defined = "\n" + bind_py_utils_functions->get_generated_code() + "\n";
815+
}
806816
if( is_string_concat_present ) {
807817
head += strcat_def;
808818
}
@@ -914,6 +924,7 @@ R"(
914924

915925
std::string body;
916926
if (compiler_options.enable_cpython) {
927+
headers.insert("Python.h");
917928
body += R"(
918929
Py_Initialize();
919930
wchar_t* argv1 = Py_DecodeLocale("", NULL);
@@ -923,6 +934,21 @@ R"(
923934
body += "\n";
924935
}
925936

937+
if (compiler_options.enable_cnumpy) {
938+
user_defines.insert("NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION");
939+
headers.insert("numpy/arrayobject.h");
940+
body +=
941+
R"( // Initialise Numpy
942+
if (_import_array() < 0) {
943+
PyErr_Print();
944+
PyErr_SetString(PyExc_ImportError, "numpy.core.multiarray failed to import");
945+
fprintf(stderr, "Failed to import numpy Python module(s)\n");
946+
return -1;
947+
}
948+
)";
949+
body += "\n";
950+
}
951+
926952
for (size_t i=0; i<x.n_body; i++) {
927953
this->visit_stmt(*x.m_body[i]);
928954
body += src;

src/libasr/codegen/asr_to_c_cpp.h

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class BaseCCPPVisitor : public ASR::BaseVisitor<Struct>
112112
// Use std::complex<float/double> or float/double complex
113113
bool gen_stdcomplex;
114114
bool is_c;
115-
std::set<std::string> headers, user_headers;
115+
std::set<std::string> headers, user_headers, user_defines;
116116
std::vector<std::string> tmp_buffer_src;
117117

118118
SymbolTable* global_scope;
@@ -123,6 +123,7 @@ class BaseCCPPVisitor : public ASR::BaseVisitor<Struct>
123123
std::string from_std_vector_helper;
124124

125125
std::unique_ptr<CCPPDSUtils> c_ds_api;
126+
std::unique_ptr<CUtils::BindPyUtilFunctions> bind_py_utils_functions;
126127
std::string const_name;
127128
size_t const_vars_count;
128129
size_t loop_end_count;
@@ -149,6 +150,7 @@ class BaseCCPPVisitor : public ASR::BaseVisitor<Struct>
149150
gen_stdstring{gen_stdstring}, gen_stdcomplex{gen_stdcomplex},
150151
is_c{is_c}, global_scope{nullptr}, lower_bound{default_lower_bound},
151152
template_number{0}, c_ds_api{std::make_unique<CCPPDSUtils>(is_c, platform)},
153+
bind_py_utils_functions{std::make_unique<CUtils::BindPyUtilFunctions>()},
152154
const_name{"constname"},
153155
const_vars_count{0}, loop_end_count{0}, bracket_open{0},
154156
is_string_concat_present{false} {
@@ -504,15 +506,23 @@ R"(#include <stdio.h>
504506
}
505507

506508
std::string get_arg_conv_bind_python(const ASR::Function_t &x) {
507-
508509
std::string arg_conv = R"(
509510
pArgs = PyTuple_New()" + std::to_string(x.n_args) + R"();
510511
)";
511512
for (size_t i = 0; i < x.n_args; ++i) {
512513
ASR::Variable_t *arg = ASRUtils::EXPR2VAR(x.m_args[i]);
514+
std::string arg_name = std::string(arg->m_name);
515+
std::string indent = "\n ";
516+
if (ASRUtils::is_array(arg->m_type)) {
517+
arg_conv += indent + bind_py_utils_functions->get_conv_dims_to_1D_arr() + "(" + arg_name + "->n_dims, " + arg_name + "->dims, __new_dims);";
518+
std::string func_call = CUtils::get_py_obj_type_conv_func_from_ttype_t(arg->m_type);
519+
arg_conv += indent + "pValue = " + func_call + "(" + arg_name + "->n_dims, __new_dims, "
520+
+ CUtils::get_numpy_c_obj_type_conv_func_from_ttype_t(arg->m_type) + ", " + arg_name + "->data);";
521+
} else {
522+
arg_conv += indent + "pValue = " + CUtils::get_py_obj_type_conv_func_from_ttype_t(arg->m_type)
523+
+ "(" + arg_name + ");";
524+
}
513525
arg_conv += R"(
514-
pValue = )" + CUtils::get_py_obj_type_conv_func_from_ttype_t(arg->m_type) + "("
515-
+ std::string(arg->m_name) + R"();
516526
if (!pValue) {
517527
Py_DECREF(pArgs);
518528
Py_DECREF(pModule);
@@ -534,15 +544,17 @@ R"(#include <stdio.h>
534544
std::string ret_var_decl = indent + CUtils::get_c_type_from_ttype_t(r_v->m_type) + " " + std::string(r_v->m_name) + ";";
535545
std::string ret_assign = indent + std::string(r_v->m_name) + " = " + py_val_cnvrt + ";";
536546
std::string ret_stmt = indent + "return " + std::string(r_v->m_name) + ";";
537-
std::string clear_pValue = "";
538-
if (!ASRUtils::is_aggregate_type(r_v->m_type)) {
539-
clear_pValue = indent + "Py_DECREF(pValue);";
547+
std::string clear_pValue = indent + "Py_DECREF(pValue);";
548+
std::string copy_result = "";
549+
if (ASRUtils::is_aggregate_type(r_v->m_type)) {
550+
if (ASRUtils::is_character(*r_v->m_type)) {
551+
copy_result = indent + std::string(r_v->m_name) + " = _lfortran_str_copy(" + std::string(r_v->m_name) + ", 1, 0);";
552+
}
540553
}
541-
return ret_var_decl + ret_assign + clear_pValue + ret_stmt + "\n";
554+
return ret_var_decl + ret_assign + copy_result + clear_pValue + ret_stmt + "\n";
542555
}
543556

544557
std::string get_func_body_bind_python(const ASR::Function_t &x) {
545-
user_headers.insert("Python.h");
546558
std::string indent(indentation_level*indentation_spaces, ' ');
547559
std::string var_decls = "PyObject *pName, *pModule, *pFunc; PyObject *pArgs, *pValue;\n";
548560
std::string func_body = R"(

0 commit comments

Comments
 (0)