3
3
import ctypes
4
4
import platform
5
5
from dataclasses import dataclass as py_dataclass , is_dataclass as py_is_dataclass
6
+ import functools
6
7
7
8
8
9
# TODO: this does not seem to restrict other imports
@@ -647,37 +648,49 @@ def ccallable(f):
647
648
def ccallback (f ):
648
649
return f
649
650
650
- class lpython :
651
- """
652
- The @lpython decorator compiles a given function using LPython.
651
+ class LpythonJITCache :
653
652
654
- The decorator should be used from CPython mode, i.e., when the module is
655
- being run using CPython. When possible, it is recommended to use LPython
656
- for the main program, and use the @cpython decorator from the LPython mode
657
- to access CPython features that are not supported by LPython.
658
- """
653
+ def __init__ (self ):
654
+ self .pyfunc2compiledfunc = {}
655
+
656
+ def compile (self , function , backend , optimisation_flags ):
657
+ if function in self .pyfunc2compiledfunc :
658
+ return self .pyfunc2compiledfunc [function ]
659
+
660
+ if optimisation_flags is not None and backend is None :
661
+ raise ValueError ("backend must be specified if backend_optimisation_flags are provided." )
662
+
663
+ if backend is None :
664
+ backend = "c"
659
665
660
- def __init__ (self , function ):
661
666
def get_rtlib_dir ():
662
667
current_dir = os .path .dirname (os .path .abspath (__file__ ))
663
668
return os .path .join (current_dir , ".." )
664
669
665
- self . fn_name = function .__name__
670
+ fn_name = function .__name__
666
671
# Get the source code of the function
667
672
source_code = getsource (function )
668
673
source_code = source_code [source_code .find ('\n ' ):]
669
674
670
- dir_name = "./lpython_decorator_" + self . fn_name
675
+ dir_name = "./lpython_decorator_" + fn_name
671
676
if not os .path .exists (dir_name ):
672
677
os .mkdir (dir_name )
673
- filename = dir_name + "/" + self . fn_name
678
+ filename = dir_name + "/" + fn_name
674
679
675
680
# Open the file for writing
676
681
with open (filename + ".py" , "w" ) as file :
677
682
# Write the Python source code to the file
678
683
file .write ("@pythoncallable" )
679
684
file .write (source_code )
680
685
686
+ if backend != "c" :
687
+ raise NotImplementedError ("Backend %s is not supported with @lpython yet." % (backend ))
688
+
689
+ opt_flags = " "
690
+ if optimisation_flags is not None :
691
+ for opt_flag in optimisation_flags :
692
+ opt_flags += opt_flag + " "
693
+
681
694
# ----------------------------------------------------------------------
682
695
# Generate the shared library
683
696
# TODO: Use LLVM instead of C backend
@@ -687,12 +700,14 @@ def get_rtlib_dir():
687
700
688
701
gcc_flags = ""
689
702
if platform .system () == "Linux" :
690
- gcc_flags = " -shared -fPIC "
703
+ gcc_flags = " -shared -fPIC"
691
704
elif platform .system () == "Darwin" :
692
- gcc_flags = " -bundle -flat_namespace -undefined suppress "
705
+ gcc_flags = " -bundle -flat_namespace -undefined suppress"
693
706
else :
694
707
raise NotImplementedError ("Platform not implemented" )
695
708
709
+ gcc_flags += opt_flags
710
+
696
711
from numpy import get_include
697
712
from distutils .sysconfig import get_python_inc , get_python_lib , \
698
713
get_python_version
@@ -706,17 +721,38 @@ def get_rtlib_dir():
706
721
707
722
# ----------------------------------------------------------------------
708
723
# Compile the C file and create a shared library
724
+ shared_library_name = "lpython_module_" + fn_name
709
725
r = os .system ("gcc -g" + gcc_flags + python_path + numpy_path +
710
- filename + ".c -o lpython_module_ " + self . fn_name + ".so " +
726
+ filename + ".c -o " + shared_library_name + ".so " +
711
727
rt_path_01 + rt_path_02 + python_lib )
712
728
assert r == 0 , "Failed to create the shared library"
729
+ self .pyfunc2compiledfunc [function ] = (shared_library_name , fn_name )
730
+ return self .pyfunc2compiledfunc [function ]
713
731
714
- def __call__ (self , * args , ** kwargs ):
715
- import sys ; sys .path .append ('.' )
716
- # import the symbol from the shared library
717
- function = getattr (__import__ ("lpython_module_" + self .fn_name ),
718
- self .fn_name )
719
- return function (* args , ** kwargs )
732
+ lpython_jit_cache = LpythonJITCache ()
733
+
734
+ # Taken from https://stackoverflow.com/a/24617244
735
+ def lpython (original_function = None , backend = None , backend_optimisation_flags = None ):
736
+ """
737
+ The @lpython decorator compiles a given function using LPython.
738
+
739
+ The decorator should be used from CPython mode, i.e., when the module is
740
+ being run using CPython. When possible, it is recommended to use LPython
741
+ for the main program, and use the @cpython decorator from the LPython mode
742
+ to access CPython features that are not supported by LPython.
743
+ """
744
+ def _lpython (function ):
745
+ @functools .wraps (function )
746
+ def __lpython (* args , ** kwargs ):
747
+ import sys ; sys .path .append ('.' )
748
+ lib_name , fn_name = lpython_jit_cache .compile (
749
+ function , backend , backend_optimisation_flags )
750
+ return getattr (__import__ (lib_name ), fn_name )(* args , ** kwargs )
751
+ return __lpython
752
+
753
+ if original_function :
754
+ return _lpython (original_function )
755
+ return _lpython
720
756
721
757
def bitnot (x , bitsize ):
722
758
return (~ x ) % (2 ** bitsize )
0 commit comments