diff --git a/integration_tests/run_tests.py b/integration_tests/run_tests.py index 9e6a5e563f..f1828400ee 100755 --- a/integration_tests/run_tests.py +++ b/integration_tests/run_tests.py @@ -3,6 +3,7 @@ import os import sys +# LPython and CPython tests tests = [ "exit_01.py", "expr_01.py", @@ -20,14 +21,15 @@ "test_math1.py" ] -# At present we run these tests on cpython, later we should also move to lpython +# CPython tests only test_cpython = [ - "test_generics_01.py" + "test_generics_01.py", + "test_c_interop_01.py" ] def main(): - print("Compiling...") + print("Compiling LPython tests...") for pyfile in tests: basename = os.path.splitext(pyfile)[0] cmd = os.path.join("..", "src", "bin", "lpython") + " %s -o %s" % (pyfile, basename) @@ -39,7 +41,7 @@ def main(): print("Command '%s' failed." % cmd) sys.exit(1) - print("Running...") + print("Running LPython and CPython tests...") python_path="src/runtime/ltypes" for pyfile in tests: basename = os.path.splitext(pyfile)[0] @@ -57,7 +59,7 @@ def main(): print("Command '%s' failed." % cmd) sys.exit(1) - print("Running cpython tests...") + print("Running CPython tests...") for pyfile in test_cpython: cmd = "PYTHONPATH=%s python integration_tests/%s" % (python_path, pyfile) diff --git a/integration_tests/test_c_interop_01.py b/integration_tests/test_c_interop_01.py new file mode 100644 index 0000000000..8b022bcfb3 --- /dev/null +++ b/integration_tests/test_c_interop_01.py @@ -0,0 +1,15 @@ +from ltypes import ccall, f64, f32 +from math import pi + +@ccall +def _lfortran_dsin(x: f64) -> f64: + pass + +@ccall +def _lfortran_ssin(x: f32) -> f32: + pass + +assert abs(_lfortran_dsin(pi) - 0) < 1e-12 +assert abs(_lfortran_dsin(pi/2) - 1) < 1e-12 +assert abs(_lfortran_ssin(pi) - 0) < 1e-6 +assert abs(_lfortran_ssin(pi/2) - 1) < 1e-6 diff --git a/src/runtime/ltypes/ltypes.py b/src/runtime/ltypes/ltypes.py index 802392fbda..71cf38d2da 100644 --- a/src/runtime/ltypes/ltypes.py +++ b/src/runtime/ltypes/ltypes.py @@ -1,5 +1,8 @@ from inspect import getfullargspec, getcallargs from typing import types +import os +import ctypes +import platform # data-types @@ -10,7 +13,7 @@ c32 = types.new_class("c32") c64 = types.new_class("c64") -# overloading support +# Overloading support def ltype(x): """ @@ -70,3 +73,59 @@ def __call__(self, *args, **kwargs): def overload(f): overloaded_f = OverloadedFunction(f) return overloaded_f + + + +# C interoperation support + +class CTypes: + """ + A wrapper class for interfacing C via ctypes. + """ + + def __init__(self, f): + def convert_type_to_ctype(arg): + if arg == f64: + return ctypes.c_double + elif arg == f32: + return ctypes.c_float + else: + raise NotImplementedError("Type not implemented") + def get_rtlib_dir(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(current_dir, "..") + def get_crtlib_name(): + if platform.system() == "Linux": + return "liblfortran_runtime.so" + elif platform.system() == "Darwin": + return "liblfortran_runtime.dylib" + elif platform.system() == "Windows": + return "lfortran_runtime.dll" + else: + raise NotImplementedError("Platform not implemented") + def get_crtlib_path(): + return os.path.join(get_rtlib_dir(), get_crtlib_name()) + self.name = f.__name__ + self.args = f.__code__.co_varnames + self.annotations = f.__annotations__ + crtlib = get_crtlib_path() + self.library = ctypes.CDLL(crtlib) + self.cf = self.library[self.name] + argtypes = [] + for arg in self.args: + arg_type = self.annotations[arg] + arg_ctype = convert_type_to_ctype(arg_type) + argtypes.append(arg_ctype) + self.cf.argtypes = argtypes + res_type = self.annotations["return"] + self.cf.restype = convert_type_to_ctype(res_type) + + def __call__(self, *args, **kwargs): + if len(kwargs) > 0: + raise Exception("kwargs are not supported") + return self.cf(*args) + + +def ccall(f): + wrapped_f = CTypes(f) + return wrapped_f