def test_access_directly_stop_at_dont_look_inside(self):
        from pypy.rlib.jit import hint, dont_look_inside

        class A:
            _virtualizable2_ = ['x']

        def h(a):
            g(a)

        h = dont_look_inside(h)

        def g(a):
            a.x = 2
            h(a)

        def f():
            a = A()
            a = hint(a, access_directly=True)
            a.x = 1
            g(a)

        t, typer, graph = self.gengraph(f, [])
        deref = typer.type_system_deref

        desc = typer.annotator.bookkeeper.getdesc(g)
        g_graphs = desc._cache.items()
        assert len(g_graphs) == 2
        g_graphs.sort()

        assert g_graphs[0][0] is None  # default
        g_graph = g_graphs[0][1]
        g_graph_directly = g_graphs[1][1]

        f_graph = t._graphof(f)
        h_graph = t._graphof(h)  # 1 graph!

        def get_direct_call_graph(graph):
            for block, op in graph.iterblockops():
                if op.opname == 'direct_call':
                    return deref(op.args[0].value).graph
            return None

        assert get_direct_call_graph(f_graph) is g_graph_directly
        assert get_direct_call_graph(g_graph) is h_graph
        assert get_direct_call_graph(g_graph_directly) is h_graph
        assert get_direct_call_graph(h_graph) is g_graph
Example #2
0
    def test_access_directly_stop_at_dont_look_inside(self):
        from pypy.rlib.jit import hint, dont_look_inside

        class A:
            _virtualizable2_ = ['x']

        def h(a):
            g(a)
        h = dont_look_inside(h)        
        
        def g(a):
            a.x = 2
            h(a)

        def f():
            a = A()
            a = hint(a, access_directly=True)
            a.x = 1
            g(a)

        t, typer, graph = self.gengraph(f, [])
        deref = typer.type_system_deref
        
        desc = typer.annotator.bookkeeper.getdesc(g)
        g_graphs = desc._cache.items()
        assert len(g_graphs) == 2
        g_graphs.sort()

        assert g_graphs[0][0] is None # default
        g_graph = g_graphs[0][1]
        g_graph_directly = g_graphs[1][1]

        f_graph = t._graphof(f)
        h_graph = t._graphof(h) # 1 graph!

        def get_direct_call_graph(graph):
            for block, op in graph.iterblockops():
                if op.opname == 'direct_call':
                    return deref(op.args[0].value).graph
            return None

        assert get_direct_call_graph(f_graph) is g_graph_directly
        assert get_direct_call_graph(g_graph) is h_graph
        assert get_direct_call_graph(g_graph_directly) is h_graph
        assert get_direct_call_graph(h_graph) is g_graph
Example #3
0
def llexternal(name, args, result, _callable=None,
               compilation_info=ExternalCompilationInfo(),
               sandboxsafe=False, threadsafe='auto',
               _nowrapper=False, calling_conv='c',
               oo_primitive=None, elidable_function=False,
               macro=None, random_effects_on_gcobjs='auto'):
    """Build an external function that will invoke the C function 'name'
    with the given 'args' types and 'result' type.

    You get by default a wrapper that casts between number types as needed
    to match the arguments.  You can also pass an RPython string when a
    CCHARP argument is expected, and the C function receives a 'const char*'
    pointing to a read-only null-terminated character of arrays, as usual
    for C.

    The C function can have callbacks, but they must be specified explicitly
    as constant RPython functions.  We don't support yet C functions that
    invoke callbacks passed otherwise (e.g. set by a previous C call).

    threadsafe: whether it's ok to release the GIL around the call.
                Default is yes, unless sandboxsafe is set, in which case
                we consider that the function is really short-running and
                don't bother releasing the GIL.  An explicit True or False
                overrides this logic.
    """
    if _callable is not None:
        assert callable(_callable)
    ext_type = lltype.FuncType(args, result)
    if _callable is None:
        if macro is not None:
            if macro is True:
                macro = name
            _callable = generate_macro_wrapper(
                name, macro, ext_type, compilation_info)
        else:
            _callable = ll2ctypes.LL2CtypesCallable(ext_type, calling_conv)
    if elidable_function:
        _callable._elidable_function_ = True
    kwds = {}
    if oo_primitive:
        kwds['oo_primitive'] = oo_primitive

    has_callback = False
    for ARG in args:
        if _isfunctype(ARG):
            has_callback = True
    if has_callback:
        kwds['_callbacks'] = callbackholder = CallbackHolder()
    else:
        callbackholder = None

    if threadsafe in (False, True):
        # invoke the around-handlers, which release the GIL, if and only if
        # the C function is thread-safe.
        invoke_around_handlers = threadsafe
    else:
        # default case:
        # invoke the around-handlers only for "not too small" external calls;
        # sandboxsafe is a hint for "too-small-ness" (e.g. math functions).
        invoke_around_handlers = not sandboxsafe

    if random_effects_on_gcobjs not in (False, True):
        random_effects_on_gcobjs = (
            invoke_around_handlers or   # because it can release the GIL
            has_callback)               # because the callback can do it

    funcptr = lltype.functionptr(ext_type, name, external='C',
                                 compilation_info=compilation_info,
                                 _callable=_callable,
                                 _safe_not_sandboxed=sandboxsafe,
                                 _debugexc=True, # on top of llinterp
                                 canraise=False,
                                 random_effects_on_gcobjs=
                                     random_effects_on_gcobjs,
                                 calling_conv=calling_conv,
                                 **kwds)
    if isinstance(_callable, ll2ctypes.LL2CtypesCallable):
        _callable.funcptr = funcptr

    if _nowrapper:
        return funcptr


    if invoke_around_handlers:
        # The around-handlers are releasing the GIL in a threaded pypy.
        # We need tons of care to ensure that no GC operation and no
        # exception checking occurs while the GIL is released.

        # The actual call is done by this small piece of non-inlinable
        # generated code in order to avoid seeing any GC pointer:
        # neither '*args' nor the GC objects originally passed in as
        # argument to wrapper(), if any (e.g. RPython strings).

        argnames = ', '.join(['a%d' % i for i in range(len(args))])
        source = py.code.Source("""
            def call_external_function(%(argnames)s):
                before = aroundstate.before
                if before: before()
                # NB. it is essential that no exception checking occurs here!
                res = funcptr(%(argnames)s)
                after = aroundstate.after
                if after: after()
                return res
        """ % locals())
        miniglobals = {'aroundstate': aroundstate,
                       'funcptr':     funcptr,
                       '__name__':    __name__, # for module name propagation
                       }
        exec source.compile() in miniglobals
        call_external_function = miniglobals['call_external_function']
        call_external_function._dont_inline_ = True
        call_external_function._annspecialcase_ = 'specialize:ll'
        call_external_function._gctransformer_hint_close_stack_ = True
        call_external_function = func_with_new_name(call_external_function,
                                                    'ccall_' + name)
        # don't inline, as a hack to guarantee that no GC pointer is alive
        # anywhere in call_external_function
    else:
        # if we don't have to invoke the aroundstate, we can just call
        # the low-level function pointer carelessly
        call_external_function = funcptr

    unrolling_arg_tps = unrolling_iterable(enumerate(args))
    def wrapper(*args):
        # XXX the next line is a workaround for the annotation bug
        # shown in rpython.test.test_llann:test_pbctype.  Remove it
        # when the test is fixed...
        assert isinstance(lltype.Signed, lltype.Number)
        real_args = ()
        to_free = ()
        for i, TARGET in unrolling_arg_tps:
            arg = args[i]
            freeme = None
            if TARGET == CCHARP:
                if arg is None:
                    arg = lltype.nullptr(CCHARP.TO)   # None => (char*)NULL
                    freeme = arg
                elif isinstance(arg, str):
                    arg = str2charp(arg)
                    # XXX leaks if a str2charp() fails with MemoryError
                    # and was not the first in this function
                    freeme = arg
            elif TARGET == CWCHARP:
                if arg is None:
                    arg = lltype.nullptr(CWCHARP.TO)   # None => (wchar_t*)NULL
                    freeme = arg
                elif isinstance(arg, unicode):
                    arg = unicode2wcharp(arg)
                    # XXX leaks if a unicode2wcharp() fails with MemoryError
                    # and was not the first in this function
                    freeme = arg
            elif TARGET is VOIDP:
                if arg is None:
                    arg = lltype.nullptr(VOIDP.TO)
                elif isinstance(arg, str):
                    arg = str2charp(arg)
                    freeme = arg
                elif isinstance(arg, unicode):
                    arg = unicode2wcharp(arg)
                    freeme = arg
            elif _isfunctype(TARGET) and not _isllptr(arg):
                # XXX pass additional arguments
                if invoke_around_handlers:
                    arg = llhelper(TARGET, _make_wrapper_for(TARGET, arg,
                                                             callbackholder,
                                                             aroundstate))
                else:
                    arg = llhelper(TARGET, _make_wrapper_for(TARGET, arg,
                                                             callbackholder))
            else:
                SOURCE = lltype.typeOf(arg)
                if SOURCE != TARGET:
                    if TARGET is lltype.Float:
                        arg = float(arg)
                    elif ((isinstance(SOURCE, lltype.Number)
                           or SOURCE is lltype.Bool)
                      and (isinstance(TARGET, lltype.Number)
                           or TARGET is lltype.Bool)):
                        arg = cast(TARGET, arg)
            real_args = real_args + (arg,)
            to_free = to_free + (freeme,)
        res = call_external_function(*real_args)
        for i, TARGET in unrolling_arg_tps:
            if to_free[i]:
                lltype.free(to_free[i], flavor='raw')
        if rarithmetic.r_int is not r_int:
            if result is INT:
                return cast(lltype.Signed, res)
            elif result is UINT:
                return cast(lltype.Unsigned, res)
        return res
    wrapper._annspecialcase_ = 'specialize:ll'
    wrapper._always_inline_ = True
    # for debugging, stick ll func ptr to that
    wrapper._ptr = funcptr
    wrapper = func_with_new_name(wrapper, name)

    if calling_conv != "c":
        wrapper = jit.dont_look_inside(wrapper)

    return wrapper