Skip to content

Initial support for @pythoncall decorator #1863

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

Merged
merged 10 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions integration_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ set_property(TARGET lpython_rtlib PROPERTY INTERFACE_LINK_LIBRARIES
${LPYTHON_RTLIB_LIBRARY})
target_link_libraries(lpython_rtlib INTERFACE m)

find_package(Python COMPONENTS Development)
message("\n")
message("System has the Python development artifacts: ${Python_Development_FOUND}")
message("The Python include directories: ${Python_INCLUDE_DIRS}")
message("The Python libraries: ${Python_LIBRARIES}")
message("The Python library directories: ${Python_LIBRARY_DIRS}")
message("The Python runtime library directories: ${Python_RUNTIME_LIBRARY_DIRS}")
message("Python version: ${Python_VERSION}")

enable_testing()

message("\n")
Expand All @@ -57,7 +66,7 @@ message("LPYTHON_RTLIB_LIBRARY: ${LPYTHON_RTLIB_LIBRARY}")


macro(RUN)
set(options FAIL)
set(options FAIL ENABLE_CPYTHON)
set(oneValueArgs NAME IMPORT_PATH)
set(multiValueArgs LABELS EXTRAFILES)
cmake_parse_arguments(RUN "${options}" "${oneValueArgs}"
Expand All @@ -68,6 +77,11 @@ macro(RUN)
message(FATAL_ERROR "Must specify the NAME argument")
endif()

set(enable_cpython "")
if (${RUN_ENABLE_CPYTHON})
set(enable_cpython "--enable-cpython")
endif()

if (${KIND} IN_LIST RUN_LABELS)
if (KIND STREQUAL "llvm")
if (import_path)
Expand Down Expand Up @@ -97,13 +111,13 @@ macro(RUN)
if (import_path)
add_custom_command(
OUTPUT ${name}.c
COMMAND ${LPYTHON} -I ${CMAKE_CURRENT_SOURCE_DIR}/${import_path} --show-c ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py > ${name}.c
COMMAND ${LPYTHON} ${enable_cpython} -I ${CMAKE_CURRENT_SOURCE_DIR}/${import_path} --show-c ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py > ${name}.c
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py
VERBATIM)
else ()
add_custom_command(
OUTPUT ${name}.c
COMMAND ${LPYTHON} --show-c ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py > ${name}.c
COMMAND ${LPYTHON} ${enable_cpython} --show-c ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py > ${name}.c
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py
VERBATIM)
endif()
Expand All @@ -112,6 +126,12 @@ macro(RUN)
target_include_directories(${name} PRIVATE ${CMAKE_SOURCE_DIR})
set_target_properties(${name} PROPERTIES LINKER_LANGUAGE C)
target_link_libraries(${name} lpython_rtlib)
if (${RUN_ENABLE_CPYTHON})
target_link_libraries(${name} Python::Python)
if (RUN_EXTRAFILES)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/${RUN_EXTRAFILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
endif()
endif()
add_test(${name} ${CMAKE_CURRENT_BINARY_DIR}/${name})
if (RUN_LABELS)
set_tests_properties(${name} PROPERTIES LABELS "${RUN_LABELS}")
Expand Down Expand Up @@ -456,6 +476,7 @@ RUN(NAME bindc_05 LABELS llvm c
EXTRAFILES bindc_05b.c)
RUN(NAME bindc_06 LABELS llvm c
EXTRAFILES bindc_06b.c)
RUN(NAME bindpy_01 LABELS cpython c ENABLE_CPYTHON EXTRAFILES bindpy_01_module.py)
RUN(NAME test_generics_01 LABELS cpython llvm c)
RUN(NAME test_cmath LABELS cpython llvm c)
RUN(NAME test_complex_01 LABELS cpython llvm c wasm wasm_x64)
Expand Down
77 changes: 77 additions & 0 deletions integration_tests/bindpy_01.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from lpython import i32, i64, u32, u64, f32, f64, pythoncall

@pythoncall(module = "bindpy_01_module")
def add_ints(a: i32, b: i64, c: u32, d: u64) -> i64:
pass

@pythoncall(module = "bindpy_01_module")
def multiply_ints(a: i32, b: i64, c: u32, d: u64) -> i64:
pass

@pythoncall(module = "bindpy_01_module")
def add_floats(a: f32, b: f64) -> f64:
pass

@pythoncall(module = "bindpy_01_module")
def multiply_floats(a: f32, b: f64) -> f64:
pass

@pythoncall(module = "bindpy_01_module")
def get_hello_world(a: str, b: str) -> str:
pass

@pythoncall(module = "bindpy_01_module")
def str_n_times(a: str, n: i32) -> str:
pass

@pythoncall(module = "bindpy_01_module")
def get_cpython_version() -> str:
pass

# Integers:
def test_ints():
i: i32
j: i64
k: u32
l: u64
i = -5
j = i64(24)
k = u32(20)
l = u64(92)

assert add_ints(i, j, k, l) == i64(131)
assert multiply_ints(i, j, k, l) == i64(-220800)

# Floats
def test_floats():
a: f32
b: f64
a = f32(3.14)
b = -100.00

assert abs(add_floats(a, b) - (-96.86)) <= 1e-4
assert abs(multiply_floats(a, b) - (-314.0)) <= 1e-4

# Strings
def test_strings():
a: str
b: str
c: str
i: i32
a = "hello"
b = "world"
i = 3

assert get_hello_world(a, b) == "hello world!"
assert str_n_times(a, i) == "hellohellohello"
assert get_hello_world(str_n_times(a, i), b) == "hellohellohello world!"

def main0():
print("CPython version: ", get_cpython_version())

test_ints()
test_floats()
test_strings()


main0()
23 changes: 23 additions & 0 deletions integration_tests/bindpy_01_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
def get_cpython_version():
import platform
return platform.python_version()

def add_ints(a, b, c, d):
e = a + b + c + d
return e

def multiply_ints(a, b, c, d):
e = a * b * c * d
return e

def add_floats(a, b):
return a + b

def multiply_floats(a, b):
return a * b

def get_hello_world(a, b):
return f"{a} {b}!"

def str_n_times(a, n):
return a * n
6 changes: 6 additions & 0 deletions src/bin/lpython.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,11 @@ int link_executable(const std::vector<std::string> &infiles,
cmd += " -I " + rtlib_header_dir;
cmd += " -L" + base_path
+ " -Wl,-rpath," + base_path + " -l" + runtime_lib + " -lm";
if (compiler_options.enable_cpython) {
std::string py_version = "3.10";
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"()";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine for now. Conda is currently required.

cmd += " " + py_flags;
}
int err = system(cmd.c_str());
if (err) {
std::cout << "The command '" + cmd + "' failed." << std::endl;
Expand Down Expand Up @@ -1556,6 +1561,7 @@ int main(int argc, char *argv[])
app.add_flag("--get-rtl-header-dir", print_rtl_header_dir, "Print the path to the runtime library header file");
app.add_flag("--get-rtl-dir", print_rtl_dir, "Print the path to the runtime library file");
app.add_flag("--verbose", compiler_options.verbose, "Print debugging statements");
app.add_flag("--enable-cpython", compiler_options.enable_cpython, "Enable CPython runtime");

// LSP specific options
app.add_flag("--show-errors", show_errors, "Show errors when LSP is running in the background");
Expand Down
9 changes: 8 additions & 1 deletion src/libasr/ASR.asdl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ symbol
bool loaded_from_mod, bool intrinsic)
| Function(symbol_table symtab, identifier name, ttype function_signature,
identifier* dependencies, expr* args, stmt* body, expr? return_var,
access access, bool deterministic, bool side_effect_free, string? c_header)
access access, bool deterministic, bool side_effect_free, string? module_file)
| GenericProcedure(symbol_table parent_symtab, identifier name,
symbol* procs, access access)
| CustomOperator(symbol_table parent_symtab, identifier name,
Expand Down Expand Up @@ -137,6 +137,12 @@ presence = Required | Optional
-- An interface that uses `iso_c_binding` and `bind(c)` is represented using
-- abi=BindC.

-- abi=BindPython: the symbol's implementation is
-- stored in text format in the user source code file.
-- The symbol is executed using the CPython interpreter.
-- LPython manages the conversion of arguments to be passed to such symbols
-- and also converts the return values from such symbols.

-- abi=Interactive: the symbol's implementation has been provided by the
-- previous REPL execution (e.g., if LLVM backend is used for the interactive
-- mode, the previous execution generated machine code for this symbol's
Expand All @@ -153,6 +159,7 @@ abi -- External ABI
| LFortranModule -- Yes LFortran
| GFortranModule -- Yes GFortran
| BindC -- Yes C
| BindPython -- Yes Python
| Interactive -- Yes Unspecified
| Intrinsic -- Yes Unspecified

Expand Down
4 changes: 2 additions & 2 deletions src/libasr/asr_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -2937,7 +2937,7 @@ inline ASR::asr_t* make_Function_t_util(Allocator& al, const Location& loc,
ASR::deftypeType m_deftype, char* m_bindc_name, bool m_elemental, bool m_pure,
bool m_module, bool m_inline, bool m_static, ASR::ttype_t** m_type_params,
size_t n_type_params, ASR::symbol_t** m_restrictions, size_t n_restrictions,
bool m_is_restriction, bool m_deterministic, bool m_side_effect_free, char *m_c_header=nullptr) {
bool m_is_restriction, bool m_deterministic, bool m_side_effect_free, char *m_module_file=nullptr) {
Vec<ASR::ttype_t*> arg_types;
arg_types.reserve(al, n_args);
ReplaceWithFunctionParamVisitor replacer(al, a_args, n_args);
Expand All @@ -2961,7 +2961,7 @@ inline ASR::asr_t* make_Function_t_util(Allocator& al, const Location& loc,
return ASR::make_Function_t(
al, loc, m_symtab, m_name, func_type, m_dependencies, n_dependencies,
a_args, n_args, m_body, n_body, m_return_var, m_access, m_deterministic,
m_side_effect_free, m_c_header);
m_side_effect_free, m_module_file);
}

class SymbolDuplicator {
Expand Down
21 changes: 21 additions & 0 deletions src/libasr/codegen/asr_to_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -890,10 +890,31 @@ R"(
}

std::string body;
if (compiler_options.enable_cpython) {
body += R"(
Py_Initialize();
wchar_t* argv1 = Py_DecodeLocale("", NULL);
wchar_t** argv_ = {&argv1};
PySys_SetArgv(1, argv_);
)";
body += "\n";
}

for (size_t i=0; i<x.n_body; i++) {
this->visit_stmt(*x.m_body[i]);
body += src;
}

if (compiler_options.enable_cpython) {
body += R"(
if (Py_FinalizeEx() < 0) {
fprintf(stderr,"BindPython: Unknown Error\n");
exit(1);
}
)";
body += "\n";
}

src = contains
+ "int main(int argc, char* argv[])\n{\n"
+ indent1 + "_lpython_set_argv(argc, argv);\n"
Expand Down
Loading