[pypy-commit] pypy sepcomp2: Start framework for a separate compilation.

amauryfa noreply at buildbot.pypy.org
Tue Feb 21 22:42:33 CET 2012


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: sepcomp2
Changeset: r52746:b97ceb68c086
Date: 2012-02-21 22:41 +0100
http://bitbucket.org/pypy/pypy/changeset/b97ceb68c086/

Log:	Start framework for a separate compilation.

diff --git a/pypy/translator/c/exportinfo.py b/pypy/translator/c/exportinfo.py
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/exportinfo.py
@@ -0,0 +1,134 @@
+from pypy.annotation import description
+from pypy.rpython.typesystem import getfunctionptr
+from pypy.translator.tool.cbuild import ExternalCompilationInfo
+from pypy.rpython.lltypesystem import lltype, rffi
+import py
+import sys
+import types
+
+class export(object):
+    """decorator to mark a function as exported by a shared module.
+    Can be used with a signature::
+        @export(float, float)
+        def f(x, y):
+            return x + y
+    or without any argument at all::
+        @export
+        def f(x, y):
+            return x + y
+    in which case the function must be used somewhere else, which will
+    trigger its annotation."""
+
+    argtypes = None
+    namespace = None
+
+    def __new__(cls, *args, **kwds):
+        if len(args) == 1 and isinstance(args[0], types.FunctionType):
+            func = args[0]
+            decorated = export()(func)
+            del decorated.argtypes
+            return decorated
+        return object.__new__(cls)
+
+    def __init__(self, *args, **kwds):
+        self.argtypes = args
+        self.namespace = kwds.pop('namespace', None)
+        if kwds:
+            raise TypeError("unexpected keyword arguments: %s" % kwds.keys())
+
+    def __call__(self, func):
+        func.exported = True
+        if self.argtypes is not None:
+            func.argtypes = self.argtypes
+        if self.namespace is not None:
+            func.namespace = self.namespace
+        return func
+
+
+class ModuleExportInfo:
+    """Translates and builds a library, and returns an 'import Module'
+    which can be used in another translation.
+
+    Using this object will generate external calls to the low-level
+    functions.
+    """
+    def __init__(self):
+        self.functions = {}
+
+    def add_function(self, name, func):
+        """Adds a function to export."""
+        self.functions[name] = func
+
+    def annotate(self, annotator):
+        """Annotate all exported functions."""
+        bk = annotator.bookkeeper
+
+        # annotate functions with signatures
+        for funcname, func in self.functions.items():
+            if hasattr(func, 'argtypes'):
+                annotator.build_types(func, func.argtypes,
+                                      complete_now=False)
+        annotator.complete()
+
+    def get_lowlevel_functions(self, annotator):
+        """Builds a map of low_level objects."""
+        bk = annotator.bookkeeper
+
+        exported_funcptr = {}
+        for name, item in self.functions.items():
+            desc = bk.getdesc(item)
+            if isinstance(desc, description.FunctionDesc):
+                graph = desc.getuniquegraph()
+                funcptr = getfunctionptr(graph)
+            else:
+                raise NotImplementedError
+
+            exported_funcptr[name] = funcptr
+        return exported_funcptr
+
+    def make_import_module(self, builder):
+        """Builds an object with all exported functions."""
+        rtyper = builder.db.translator.rtyper
+
+        exported_funcptr = self.get_lowlevel_functions(
+            builder.translator.annotator)
+        # Map exported functions to the names given by the translator.
+        node_names = dict(
+            (funcname, builder.db.get(funcptr))
+            for funcname, funcptr in exported_funcptr.items())
+
+        # Declarations of functions defined in the first module.
+        forwards = []
+        for node in builder.db.globalcontainers():
+            if node.nodekind == 'func' and node.name in node_names.values():
+                forwards.append('\n'.join(node.forward_declaration()))
+
+        so_name = py.path.local(builder.so_name)
+
+        if sys.platform == 'win32':
+            libraries = [so_name.purebasename]
+        else:
+            libraries = [so_name.purebasename[3:]]
+
+        import_eci = ExternalCompilationInfo(
+            libraries=libraries,
+            library_dirs=[so_name.dirname],
+            post_include_bits=forwards,
+            )
+        class Module(object):
+            __file__ = builder.so_name
+        mod = Module()
+        for funcname, funcptr in exported_funcptr.items():
+            import_name = node_names[funcname]
+            func = make_llexternal_function(import_name, funcptr, import_eci)
+            setattr(mod, funcname, func)
+        return mod
+
+def make_llexternal_function(name, funcptr, eci):
+    functype = lltype.typeOf(funcptr)
+    imported_func = rffi.llexternal(
+        name, functype.TO.ARGS, functype.TO.RESULT,
+        compilation_info=eci,
+        )
+    return imported_func
+
diff --git a/pypy/translator/c/test/test_export.py b/pypy/translator/c/test/test_export.py
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/test/test_export.py
@@ -0,0 +1,53 @@
+from pypy.translator.translator import TranslationContext
+from pypy.translator.c.exportinfo import export, ModuleExportInfo
+from pypy.translator.c.dlltool import CLibraryBuilder
+from pypy.translator.tool.cbuild import ExternalCompilationInfo
+import sys
+
+class TestExportFunctions:
+    def setup_method(self, method):
+        self.additional_PATH = []
+
+    def compile_module(self, modulename, **exports):
+        export_info = ModuleExportInfo()
+        for name, obj in exports.items():
+            export_info.add_function(name, obj)
+
+        t = TranslationContext()
+        t.buildannotator()
+        export_info.annotate(t.annotator)
+        t.buildrtyper().specialize()
+
+        functions = [(f, None) for f in export_info.functions.values()]
+        builder = CLibraryBuilder(t, None, config=t.config,
+                                  name='lib' + modulename,
+                                  functions=functions)
+        if sys.platform != 'win32' and self.additional_PATH:
+            builder.merge_eci(ExternalCompilationInfo(
+                    link_extra=['-Wl,-rpath,%s' % path for path in
+                                self.additional_PATH]))
+        builder.modulename = 'lib' + modulename
+        builder.generate_source()
+        builder.compile()
+
+        mod = export_info.make_import_module(builder)
+
+        filepath = builder.so_name.dirpath()
+        self.additional_PATH.append(filepath)
+
+        return mod
+
+    def test_simple_call(self):
+        # function exported from the 'first' module
+        @export(float)
+        def f(x):
+            return x + 42.3
+        firstmodule = self.compile_module("first", f=f)
+        
+        # call it from a function compiled in another module
+        @export()
+        def g():
+            return firstmodule.f(12.0)
+        secondmodule = self.compile_module("second", g=g)
+
+        assert secondmodule.g() == 54.3


More information about the pypy-commit mailing list