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
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