inline.py 5.98 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#! /usr/bin/env python
#
# Copyright (C) 2007 Tiago de Paula Peixoto <tiago@forked.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

Tiago Peixoto's avatar
Tiago Peixoto committed
18
import sys, string, hashlib, os.path
19 20 21 22 23 24 25 26 27
from .. import core
from .. import libgraph_tool_core

try:
    import scipy.weave
except ImportError:
    raise libgraph_tool_core.raise_error\
          ("You need to have scipy installed to use 'run_action'.")

Tiago Peixoto's avatar
Tiago Peixoto committed
28 29 30 31 32 33 34 35
prefix = None
for d in [d + "/graph_tool" for d in sys.path]:
    if os.path.exists(d):
        prefix = d
        break

inc_prefix = prefix + "/include"
cxxflags = libgraph_tool_core.mod_info().cxxflags + " -I%s" % inc_prefix
36 37

# this is the code template which defines the action function object
Tiago Peixoto's avatar
Tiago Peixoto committed
38
support_template = open(prefix + "/run_action/run_action_template.hh").read()
39 40 41 42 43 44

def inline(g, code, arg_names=[], local_dict=None,
           global_dict=None, force=0, compiler="gcc", verbose=0,
           auto_downcast=1, support_code="", libraries=[],
           library_dirs=[], extra_compile_args=[],
           runtime_library_dirs=[], extra_objects=[],
45
           extra_link_args=[], mask_ret=[]):
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
    """Compile (if necessary) and run the C++ action specified by 'code',
    using weave."""

    # we need to have different template names for each actions, to avoid
    # strange RTTI issues. We'll therefore append an md5 hash of the code (plus
    # grah_tool version string) to each action's name
    import hashlib
    code_hash = hashlib.md5(code + core.__version__).hexdigest()

    # property map types
    props = ""
    for d in ["vertex", "edge", "graph"]:
        for t in core.value_types():
            props += ("typedef typename %s_prop_t::template as<%s >::type" + \
                      " %sprop_%s_t;\n") % \
                      (d,t,d[0],t.replace(" ","_").replace("::","_")\
                       .replace("<","_").replace(">","_"))

    # each term on the expansion will properly unwrap a tuple pointer value
    # to a reference with the appropriate name and type
    exp_term = """typename boost::remove_pointer<typename element<%d,Args>::type>::type& %s =
                          *get<%d>(_args);"""
    arg_expansion = "\n".join([ exp_term % (i,arg_names[i],i) for i in \
                                xrange(0, len(arg_names))])

    # handle returned values
    return_vals = ""
    for arg in arg_names:
74 75
        if arg not in mask_ret:
            return_vals += 'return_vals["%s"] = %s;\n' % (arg, arg)
76 77 78

    support_template = string.Template(globals()["support_template"])

Tiago Peixoto's avatar
Tiago Peixoto committed
79 80 81 82 83
    support_code += support_template.substitute(code_hash=code_hash,
                                                property_map_types=props,
                                                arg_expansion=arg_expansion,
                                                code=code,
                                                return_vals = return_vals)
84 85 86 87 88 89 90 91 92 93 94 95

    # insert a hash value of the support_code into the code below, to force
    # recompilation when support_code (and module version) changes
    support_hash = hashlib.md5(support_code + core.__version__).hexdigest()

    # the actual inline code will just call g.RunAction() on the underlying
    # GraphInterface instance. The inline arguments will be packed into a
    # tuple of pointers.
    code = string.Template(r"""
    python::object pg(python::handle<>
                        (python::borrowed((PyObject*)(self___graph))));
    GraphInterface& g = python::extract<GraphInterface&>(pg);
96
    RunAction(g, make_action(boost::make_tuple(${args}), return_val));
97 98 99 100 101 102 103 104
    // support code hash: ${support_hash}
    """).substitute(args=", ".join(["&%s" %a for a in arg_names]),
                    code_hash=code_hash, support_hash=support_hash)

    # we need to get the locals and globals of the _calling_ function. Thus, we
    # need to go deeper into the call stack
    call_frame = sys._getframe(1)
    if local_dict is None:
Tiago Peixoto's avatar
Tiago Peixoto committed
105
        local_dict = call_frame.f_locals
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
    if global_dict is None:
        global_dict = call_frame.f_globals
    local_dict["self___graph"] = g.underlying_graph() # the graph interface

    # RTLD_GLOBAL needs to be set in dlopen() if we want typeinfo and
    # friends to work properly across DSO boundaries. See
    # http://gcc.gnu.org/faq.html#dso
    orig_dlopen_flags = sys.getdlopenflags()
    sys.setdlopenflags(core.RTLD_NOW|core.RTLD_GLOBAL)

    # call weave and pass all the updated kw arguments
    ret_vals = \
             scipy.weave.inline(code, ["self___graph"] + arg_names, force=force,
                                local_dict=local_dict, global_dict=global_dict,
                                compiler=compiler, verbose=verbose,
                                auto_downcast=auto_downcast,
                                support_code=support_code,
                                libraries=libraries,
                                library_dirs=sys.path + library_dirs,
                                extra_compile_args=[cxxflags] + \
                                        extra_compile_args,
                                runtime_library_dirs=runtime_library_dirs,
                                extra_objects=extra_objects,
                                extra_link_args=extra_link_args)
    # check if exception was thrown
    if ret_vals["__exception_thrown"]:
        libgraph_tool_core.raise_error(ret_vals["__exception_error"])

    sys.setdlopenflags(orig_dlopen_flags) # reset dlopen to normal case to
                                          # avoid unnecessary symbol collision
Tiago Peixoto's avatar
Tiago Peixoto committed
136
    return ret_vals