def getfunctionptr(graph, getconcretetype=None): """Return callable given a Python function.""" if getconcretetype is None: getconcretetype = _getconcretetype llinputs = [getconcretetype(v) for v in graph.getargs()] lloutput = getconcretetype(graph.getreturnvar()) FT = lltype.FuncType(llinputs, lloutput) name = graph.name if hasattr(graph, 'func') and callable(graph.func): # the Python function object can have _llfnobjattrs_, specifying # attributes that are forced upon the functionptr(). The idea # for not passing these extra attributes as arguments to # getcallable() itself is that multiple calls to getcallable() # for the same graph should return equal functionptr() objects. if hasattr(graph.func, '_llfnobjattrs_'): fnobjattrs = graph.func._llfnobjattrs_.copy() # can specify a '_name', but use graph.name by default name = fnobjattrs.pop('_name', name) else: fnobjattrs = {} # _callable is normally graph.func, but can be overridden: # see fakeimpl in extfunc.py _callable = fnobjattrs.pop('_callable', graph.func) return lltype.functionptr(FT, name, graph = graph, _callable = _callable, **fnobjattrs) else: return lltype.functionptr(FT, name, graph = graph)
def getfunctionptr(graph, getconcretetype=None): """Return callable given a Python function.""" if getconcretetype is None: getconcretetype = _getconcretetype llinputs = [getconcretetype(v) for v in graph.getargs()] lloutput = getconcretetype(graph.getreturnvar()) FT = lltype.FuncType(llinputs, lloutput) name = graph.name if hasattr(graph, 'func') and callable(graph.func): # the Python function object can have _llfnobjattrs_, specifying # attributes that are forced upon the functionptr(). The idea # for not passing these extra attributes as arguments to # getcallable() itself is that multiple calls to getcallable() # for the same graph should return equal functionptr() objects. if hasattr(graph.func, '_llfnobjattrs_'): fnobjattrs = graph.func._llfnobjattrs_.copy() # can specify a '_name', but use graph.name by default name = fnobjattrs.pop('_name', name) else: fnobjattrs = {} # _callable is normally graph.func, but can be overridden: # see fakeimpl in extfunc.py _callable = fnobjattrs.pop('_callable', graph.func) return lltype.functionptr(FT, name, graph=graph, _callable=_callable, **fnobjattrs) else: return lltype.functionptr(FT, name, graph=graph)
def test_caching_dynamic_deallocator(): S = lltype.GcStruct("S", ('x', lltype.Signed), rtti=True) S1 = lltype.GcStruct("S1", ('s', S), ('y', lltype.Signed), rtti=True) T = lltype.GcStruct("T", ('x', lltype.Signed), rtti=True) def f_S(s): s.x = 1 def f_S1(s1): s1.s.x = 1 s1.y = 2 def f_T(s): s.x = 1 def type_info_S(p): return lltype.getRuntimeTypeInfo(S) def type_info_T(p): return lltype.getRuntimeTypeInfo(T) qp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Ptr( lltype.RuntimeTypeInfo)), "type_info_S", _callable=type_info_S) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Void), "destructor_funcptr", _callable=f_S) pinf = lltype.attachRuntimeTypeInfo(S, qp, destrptr=dp) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Void), "destructor_funcptr", _callable=f_S1) pinf = lltype.attachRuntimeTypeInfo(S1, qp, destrptr=dp) qp = lltype.functionptr(lltype.FuncType([lltype.Ptr(T)], lltype.Ptr( lltype.RuntimeTypeInfo)), "type_info_S", _callable=type_info_T) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(T)], lltype.Void), "destructor_funcptr", _callable=f_T) pinf = lltype.attachRuntimeTypeInfo(T, qp, destrptr=dp) def f(): pass t = TranslationContext() t.buildannotator().build_types(f, []) t.buildrtyper().specialize() transformer = RefcountingGCTransformer(t) p_S = transformer.dynamic_deallocation_funcptr_for_type(S) p_S1 = transformer.dynamic_deallocation_funcptr_for_type(S1) p_T = transformer.dynamic_deallocation_funcptr_for_type(T) assert p_S is not p_T assert p_S is p_S1
def test_caching_dynamic_deallocator(): S = lltype.GcStruct("S", ('x', lltype.Signed), rtti=True) S1 = lltype.GcStruct("S1", ('s', S), ('y', lltype.Signed), rtti=True) T = lltype.GcStruct("T", ('x', lltype.Signed), rtti=True) def f_S(s): s.x = 1 def f_S1(s1): s1.s.x = 1 s1.y = 2 def f_T(s): s.x = 1 def type_info_S(p): return lltype.getRuntimeTypeInfo(S) def type_info_T(p): return lltype.getRuntimeTypeInfo(T) qp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Ptr(lltype.RuntimeTypeInfo)), "type_info_S", _callable=type_info_S) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Void), "destructor_funcptr", _callable=f_S) pinf = lltype.attachRuntimeTypeInfo(S, qp, destrptr=dp) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Void), "destructor_funcptr", _callable=f_S1) pinf = lltype.attachRuntimeTypeInfo(S1, qp, destrptr=dp) qp = lltype.functionptr(lltype.FuncType([lltype.Ptr(T)], lltype.Ptr(lltype.RuntimeTypeInfo)), "type_info_S", _callable=type_info_T) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(T)], lltype.Void), "destructor_funcptr", _callable=f_T) pinf = lltype.attachRuntimeTypeInfo(T, qp, destrptr=dp) def f(): pass t = TranslationContext() t.buildannotator().build_types(f, []) t.buildrtyper().specialize() transformer = RefcountingGCTransformer(t) p_S = transformer.dynamic_deallocation_funcptr_for_type(S) p_S1 = transformer.dynamic_deallocation_funcptr_for_type(S1) p_T = transformer.dynamic_deallocation_funcptr_for_type(T) assert p_S is not p_T assert p_S is p_S1
def _setup_repr_final(self): AbstractInstanceRepr._setup_repr_final(self) if self.gcflavor == 'gc': if (self.classdef is not None and self.classdef.classdesc.lookup('__del__') is not None): s_func = self.classdef.classdesc.s_read_attribute('__del__') source_desc = self.classdef.classdesc.lookup('__del__') source_classdef = source_desc.getclassdef(None) source_repr = getinstancerepr(self.rtyper, source_classdef) assert len(s_func.descriptions) == 1 funcdesc, = s_func.descriptions graph = funcdesc.getuniquegraph() self.check_graph_of_del_does_not_call_too_much(graph) FUNCTYPE = FuncType([Ptr(source_repr.object_type)], Void) destrptr = functionptr(FUNCTYPE, graph.name, graph=graph, _callable=graph.func) else: destrptr = None OBJECT = OBJECT_BY_FLAVOR[LLFLAVOR[self.gcflavor]] self.rtyper.attachRuntimeTypeInfoFunc(self.object_type, ll_runtime_type_info, OBJECT, destrptr) vtable = self.rclass.getvtable() self.rtyper.set_type_for_typeptr(vtable, self.lowleveltype.TO)
def _setup_repr_final(self): self._setup_immutable_field_list() self._check_for_immutable_conflicts() if self.gcflavor == 'gc': if (self.classdef is not None and self.classdef.classdesc.lookup('__del__') is not None): s_func = self.classdef.classdesc.s_read_attribute('__del__') source_desc = self.classdef.classdesc.lookup('__del__') source_classdef = source_desc.getclassdef(None) source_repr = getinstancerepr(self.rtyper, source_classdef) assert len(s_func.descriptions) == 1 funcdesc, = s_func.descriptions graph = funcdesc.getuniquegraph() self.check_graph_of_del_does_not_call_too_much( self.rtyper, graph) FUNCTYPE = FuncType([Ptr(source_repr.object_type)], Void) destrptr = functionptr(FUNCTYPE, graph.name, graph=graph, _callable=graph.func) else: destrptr = None self.rtyper.call_all_setups() # compute ForwardReferences now args_s = [SomePtr(Ptr(OBJECT))] graph = self.rtyper.annotate_helper(ll_runtime_type_info, args_s) s = self.rtyper.annotation(graph.getreturnvar()) if (not isinstance(s, SomePtr) or s.ll_ptrtype != Ptr(RuntimeTypeInfo)): raise TyperError("runtime type info function returns %r, " "expected Ptr(RuntimeTypeInfo)" % (s)) funcptr = self.rtyper.getcallable(graph) attachRuntimeTypeInfo(self.object_type, funcptr, destrptr) vtable = self.rclass.getvtable() self.rtyper.set_type_for_typeptr(vtable, self.lowleveltype.TO)
def test_assemble_cast_consts(): ssarepr = SSARepr("test") S = lltype.GcStruct('S') s = lltype.malloc(S) F = lltype.FuncType([], lltype.Signed) f = lltype.functionptr(F, 'f') ssarepr.insns = [ ('int_return', Constant('X', lltype.Char)), ('int_return', Constant(unichr(0x1234), lltype.UniChar)), ('int_return', Constant(f, lltype.Ptr(F))), ('ref_return', Constant(s, lltype.Ptr(S))), ] assembler = Assembler() jitcode = assembler.assemble(ssarepr) assert jitcode.code == ("\x00\x58" "\x01\xFF" "\x01\xFE" "\x02\xFF") assert assembler.insns == {'int_return/c': 0, 'int_return/i': 1, 'ref_return/r': 2} f_int = heaptracker.adr2int(llmemory.cast_ptr_to_adr(f)) assert jitcode.constants_i == [0x1234, f_int] s_gcref = lltype.cast_opaque_ptr(llmemory.GCREF, s) assert jitcode.constants_r == [s_gcref]
def test_decode_builtin_call_method(): A = lltype.GcArray(lltype.Signed) def myfoobar(a, i, marker, c): assert marker == 'mymarker' return a[i] * ord(c) myfoobar.oopspec = 'spam.foobar(a, 2, c, i)' TYPE = lltype.FuncType([lltype.Ptr(A), lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed myarray = lltype.malloc(A, 10) myarray[5] = 42 op = SpaceOperation('direct_call', [newconst(fnobj), newconst(myarray), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'spam.foobar' assert opargs == [newconst(myarray), newconst(2), vc, vi]
def test_llexternal(self): from rpython.rtyper.lltypesystem.rffi import llexternal from rpython.rtyper.lltypesystem import lltype z = llexternal('z', [lltype.Signed], lltype.Signed) def f(x): return z(x) t, ra = self.translate(f, [int]) fgraph = graphof(t, f) backend_optimizations(t) assert fgraph.startblock.operations[0].opname == 'direct_call' result = ra.can_raise(fgraph.startblock.operations[0]) assert not result z = lltype.functionptr(lltype.FuncType([lltype.Signed], lltype.Signed), 'foobar') def g(x): return z(x) t, ra = self.translate(g, [int]) ggraph = graphof(t, g) assert ggraph.startblock.operations[0].opname == 'direct_call' result = ra.can_raise(ggraph.startblock.operations[0]) assert result
def _setup_repr_final(self): self._setup_immutable_field_list() self._check_for_immutable_conflicts() if self.gcflavor == 'gc': if (self.classdef is not None and self.classdef.classdesc.lookup('__del__') is not None): s_func = self.classdef.classdesc.s_read_attribute('__del__') source_desc = self.classdef.classdesc.lookup('__del__') source_classdef = source_desc.getclassdef(None) source_repr = getinstancerepr(self.rtyper, source_classdef) assert len(s_func.descriptions) == 1 funcdesc, = s_func.descriptions graph = funcdesc.getuniquegraph() self.check_graph_of_del_does_not_call_too_much(graph) FUNCTYPE = FuncType([Ptr(source_repr.object_type)], Void) destrptr = functionptr(FUNCTYPE, graph.name, graph=graph, _callable=graph.func) else: destrptr = None self.rtyper.call_all_setups() # compute ForwardReferences now args_s = [SomePtr(Ptr(OBJECT))] graph = self.rtyper.annotate_helper(ll_runtime_type_info, args_s) s = self.rtyper.annotation(graph.getreturnvar()) if (not isinstance(s, SomePtr) or s.ll_ptrtype != Ptr(RuntimeTypeInfo)): raise TyperError("runtime type info function returns %r, " "expected Ptr(RuntimeTypeInfo)" % (s)) funcptr = self.rtyper.getcallable(graph) attachRuntimeTypeInfo(self.object_type, funcptr, destrptr) vtable = self.rclass.getvtable() self.rtyper.set_type_for_typeptr(vtable, self.lowleveltype.TO)
def test_deallocator_with_destructor(): S = lltype.GcStruct("S", ('x', lltype.Signed), rtti=True) def f(s): s.x = 1 def type_info_S(p): return lltype.getRuntimeTypeInfo(S) qp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Ptr(lltype.RuntimeTypeInfo)), "type_info_S", _callable=type_info_S) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Void), "destructor_funcptr", _callable=f) pinf = lltype.attachRuntimeTypeInfo(S, qp, destrptr=dp) graph, t = make_deallocator(S)
def test_decode_builtin_call_method(): A = lltype.GcArray(lltype.Signed) def myfoobar(a, i, marker, c): assert marker == 'mymarker' return a[i] * ord(c) myfoobar.oopspec = 'spam.foobar(a, 2, c, i)' TYPE = lltype.FuncType( [lltype.Ptr(A), lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed myarray = lltype.malloc(A, 10) myarray[5] = 42 op = SpaceOperation( 'direct_call', [newconst(fnobj), newconst(myarray), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'spam.foobar' assert opargs == [newconst(myarray), newconst(2), vc, vi]
def test_assemble_cast_consts(): ssarepr = SSARepr("test") S = lltype.GcStruct('S') s = lltype.malloc(S) np = lltype.nullptr(S) F = lltype.FuncType([], lltype.Signed) f = lltype.functionptr(F, 'f') ssarepr.insns = [ ('int_return', Constant('X', lltype.Char)), ('int_return', Constant(unichr(0x1234), lltype.UniChar)), ('int_return', Constant(f, lltype.Ptr(F))), ('ref_return', Constant(s, lltype.Ptr(S))), ('ref_return', Constant(np, lltype.Ptr(S))), ('ref_return', Constant(s, lltype.Ptr(S))), ('ref_return', Constant(np, lltype.Ptr(S))), ] assembler = Assembler() jitcode = assembler.assemble(ssarepr) assert jitcode.code == ("\x00\x58" "\x01\xFF" "\x01\xFE" "\x02\xFF" "\x02\xFE" "\x02\xFF" "\x02\xFE") assert assembler.insns == { 'int_return/c': 0, 'int_return/i': 1, 'ref_return/r': 2 } f_int = ptr2int(f) assert jitcode.constants_i == [0x1234, f_int] s_gcref = lltype.cast_opaque_ptr(llmemory.GCREF, s) np_gcref = lltype.cast_opaque_ptr(llmemory.GCREF, np) assert jitcode.constants_r == [s_gcref, np_gcref]
def specialize_call(self, hop): rtyper = hop.rtyper signature_args = self.normalize_args(*hop.args_s) args_r = [rtyper.getrepr(s_arg) for s_arg in signature_args] args_ll = [r_arg.lowleveltype for r_arg in args_r] s_result = hop.s_result r_result = rtyper.getrepr(s_result) ll_result = r_result.lowleveltype name = getattr(self, 'name', None) or self.instance.__name__ impl = getattr(self, 'lltypeimpl', None) fakeimpl = getattr(self, 'lltypefakeimpl', self.instance) if impl: if hasattr(self, 'lltypefakeimpl'): # If we have both an llimpl and an llfakeimpl, # we need a wrapper that selects the proper one and calls it from rpython.tool.sourcetools import func_with_new_name # Using '*args' is delicate because this wrapper is also # created for init-time functions like llarena.arena_malloc # which are called before the GC is fully initialized args = ', '.join(['arg%d' % i for i in range(len(args_ll))]) d = {'original_impl': impl, 's_result': s_result, 'fakeimpl': fakeimpl, '__name__': __name__, } exec py.code.compile(""" from rpython.rlib.objectmodel import running_on_llinterp from rpython.rlib.debug import llinterpcall from rpython.rlib.jit import dont_look_inside # note: we say 'dont_look_inside' mostly because the # JIT does not support 'running_on_llinterp', but in # theory it is probably right to stop jitting anyway. @dont_look_inside def ll_wrapper(%s): if running_on_llinterp: return llinterpcall(s_result, fakeimpl, %s) else: return original_impl(%s) """ % (args, args, args)) in d impl = func_with_new_name(d['ll_wrapper'], name + '_wrapper') if rtyper.annotator.translator.config.translation.sandbox: impl._dont_inline_ = True # store some attributes to the 'impl' function, where # the eventual call to rtyper.getcallable() will find them # and transfer them to the final lltype.functionptr(). impl._llfnobjattrs_ = { '_name': self.name, '_safe_not_sandboxed': self.safe_not_sandboxed, } obj = rtyper.getannmixlevel().delayedfunction( impl, signature_args, hop.s_result) else: FT = FuncType(args_ll, ll_result) obj = functionptr(FT, name, _external_name=self.name, _callable=fakeimpl, _safe_not_sandboxed=self.safe_not_sandboxed) vlist = [hop.inputconst(typeOf(obj), obj)] + hop.inputargs(*args_r) hop.exception_is_here() return hop.genop('direct_call', vlist, r_result)
def test_boehm_finalizer___del__(): S = lltype.GcStruct("S", ('x', lltype.Signed), rtti=True) def f(s): s.x = 1 def type_info_S(p): return lltype.getRuntimeTypeInfo(S) qp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Ptr(lltype.RuntimeTypeInfo)), "type_info_S", _callable=type_info_S) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Void), "destructor_funcptr", _callable=f) lltype.attachRuntimeTypeInfo(S, qp, destrptr=dp) f, t = make_boehm_finalizer(S) assert f is not None
def annotate(translator, func, result, args): args = [arg.concretetype for arg in args] graph = translator.rtyper.annotate_helper(func, args) fptr = lltype.functionptr(lltype.FuncType(args, result.concretetype), func.func_name, graph=graph) c = inputconst(lltype.typeOf(fptr), fptr) return c
def genexternalcall(self, fnname, args_v, resulttype=None, **flags): if isinstance(resulttype, Repr): resulttype = resulttype.lowleveltype argtypes = [v.concretetype for v in args_v] FUNCTYPE = FuncType(argtypes, resulttype or Void) f = functionptr(FUNCTYPE, fnname, **flags) cf = inputconst(typeOf(f), f) return self.genop('direct_call', [cf] + list(args_v), resulttype)
def genexternalcall(self, fnname, args_v, resulttype=None, **flags): if isinstance(resulttype, Repr): resulttype = resulttype.lowleveltype argtypes = [v.concretetype for v in args_v] FUNCTYPE = FuncType(argtypes, resulttype or Void) f = functionptr(FUNCTYPE, fnname, **flags) cf = inputconst(typeOf(f), f) return self.genop('direct_call', [cf]+list(args_v), resulttype)
def get_direct_call_op(argtypes, restype): FUNC = lltype.FuncType(argtypes, restype) fnptr = lltype.functionptr(FUNC, "g") # no graph c_fnptr = const(fnptr) vars = [varoftype(TYPE) for TYPE in argtypes] v_result = varoftype(restype) op = SpaceOperation("direct_call", [c_fnptr] + vars, v_result) return op
def test_half_exceptiontransformed_graphs(): from rpython.translator import exceptiontransform def f1(x): if x < 0: raise ValueError return 754 def g1(x): try: return f1(x) except ValueError: return 5 def f2(x): if x < 0: raise ValueError return 21 def g2(x): try: return f2(x) except ValueError: return 6 f3 = lltype.functionptr(lltype.FuncType([lltype.Signed], lltype.Signed), 'f3', _callable=f1) def g3(x): try: return f3(x) except ValueError: return 7 def f(flag, x): if flag == 1: return g1(x) elif flag == 2: return g2(x) else: return g3(x) t = TranslationContext() t.buildannotator().build_types(f, [int, int]) t.buildrtyper().specialize() etrafo = exceptiontransform.ExceptionTransformer(t) etrafo.create_exception_handling(graphof(t, f1)) etrafo.create_exception_handling(graphof(t, g2)) etrafo.create_exception_handling(graphof(t, g3)) graph = graphof(t, f) interp = LLInterpreter(t.rtyper) res = interp.eval_graph(graph, [1, -64]) assert res == 5 res = interp.eval_graph(graph, [2, -897]) assert res == 6 res = interp.eval_graph(graph, [3, -9831]) assert res == 7
def test_half_exceptiontransformed_graphs(): from rpython.translator import exceptiontransform def f1(x): if x < 0: raise ValueError return 754 def g1(x): try: return f1(x) except ValueError: return 5 def f2(x): if x < 0: raise ValueError return 21 def g2(x): try: return f2(x) except ValueError: return 6 f3 = lltype.functionptr(lltype.FuncType([lltype.Signed], lltype.Signed), "f3", _callable=f1) def g3(x): try: return f3(x) except ValueError: return 7 def f(flag, x): if flag == 1: return g1(x) elif flag == 2: return g2(x) else: return g3(x) t = TranslationContext() t.buildannotator().build_types(f, [int, int]) t.buildrtyper().specialize() etrafo = exceptiontransform.ExceptionTransformer(t) etrafo.create_exception_handling(graphof(t, f1)) etrafo.create_exception_handling(graphof(t, g2)) etrafo.create_exception_handling(graphof(t, g3)) graph = graphof(t, f) interp = LLInterpreter(t.rtyper) res = interp.eval_graph(graph, [1, -64]) assert res == 5 res = interp.eval_graph(graph, [2, -897]) assert res == 6 res = interp.eval_graph(graph, [3, -9831]) assert res == 7
def test_deallocator_with_destructor(): S = lltype.GcStruct("S", ('x', lltype.Signed), rtti=True) def f(s): s.x = 1 def type_info_S(p): return lltype.getRuntimeTypeInfo(S) qp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Ptr( lltype.RuntimeTypeInfo)), "type_info_S", _callable=type_info_S) dp = lltype.functionptr(lltype.FuncType([lltype.Ptr(S)], lltype.Void), "destructor_funcptr", _callable=f) pinf = lltype.attachRuntimeTypeInfo(S, qp, destrptr=dp) graph, t = make_deallocator(S)
def test_graphs_from_direct_call(): cc = CallControl() F = lltype.FuncType([], lltype.Signed) f = lltype.functionptr(F, 'f', graph='fgraph') v = varoftype(lltype.Signed) op = SpaceOperation('direct_call', [Constant(f, lltype.Ptr(F))], v) # lst = cc.graphs_from(op, {}.__contains__) assert lst is None # residual call # lst = cc.graphs_from(op, {'fgraph': True}.__contains__) assert lst == ['fgraph'] # normal call
def get_funcptr(self, rtyper, args_r, r_result): from rpython.rtyper.rtyper import llinterp_backend args_ll = [r_arg.lowleveltype for r_arg in args_r] ll_result = r_result.lowleveltype name = self.s_func.name if self.fakeimpl and rtyper.backend is llinterp_backend: FT = FuncType(args_ll, ll_result) return functionptr( FT, name, _external_name=name, _callable=self.fakeimpl) elif self.impl: if isinstance(self.impl, _ptr): return self.impl else: # store some attributes to the 'impl' function, where # the eventual call to rtyper.getcallable() will find them # and transfer them to the final lltype.functionptr(). self.impl._llfnobjattrs_ = {'_name': name} return rtyper.getannmixlevel().delayedfunction( self.impl, self.s_func.args_s, self.s_func.s_result) else: fakeimpl = self.fakeimpl or self.s_func.const FT = FuncType(args_ll, ll_result) return functionptr( FT, name, _external_name=name, _callable=fakeimpl)
def test_str2unicode(): # test that the oopspec is present and correctly transformed PSTR = lltype.Ptr(rstr.STR) PUNICODE = lltype.Ptr(rstr.UNICODE) FUNC = lltype.FuncType([PSTR], PUNICODE) func = lltype.functionptr(FUNC, "ll_str2unicode", _callable=rstr.LLHelpers.ll_str2unicode) v1 = varoftype(PSTR) v2 = varoftype(PUNICODE) op = SpaceOperation("direct_call", [const(func), v1], v2) tr = Transformer(FakeCPU(), FakeBuiltinCallControl()) op1 = tr.rewrite_operation(op) assert op1.opname == "residual_call_r_r" assert op1.args[0].value == func assert op1.args[1] == ListOfKind("ref", [v1]) assert op1.args[2] == "calldescr-%d" % effectinfo.EffectInfo.OS_STR2UNICODE assert op1.result == v2
def test_math_sqrt(): # test that the oopspec is present and correctly transformed FLOAT = lltype.Float FUNC = lltype.FuncType([FLOAT], FLOAT) func = lltype.functionptr(FUNC, "ll_math", _callable=ll_math.sqrt_nonneg) v1 = varoftype(FLOAT) v2 = varoftype(FLOAT) op = SpaceOperation("direct_call", [const(func), v1], v2) tr = Transformer(FakeCPU(), FakeBuiltinCallControl()) op1 = tr.rewrite_operation(op) assert op1.opname == "residual_call_irf_f" assert op1.args[0].value == func assert op1.args[1] == ListOfKind("int", []) assert op1.args[2] == ListOfKind("ref", []) assert op1.args[3] == ListOfKind("float", [v1]) assert op1.args[4] == "calldescr-%d" % effectinfo.EffectInfo.OS_MATH_SQRT assert op1.result == v2
def test_malloc_new_with_destructor(): vtable = lltype.malloc(rclass.OBJECT_VTABLE, immortal=True) S = lltype.GcStruct("S", ("parent", rclass.OBJECT), rtti=True) DESTRUCTOR = lltype.FuncType([lltype.Ptr(S)], lltype.Void) destructor = lltype.functionptr(DESTRUCTOR, "destructor") lltype.attachRuntimeTypeInfo(S, destrptr=destructor) heaptracker.set_testing_vtable_for_gcstruct(S, vtable, "S") v = varoftype(lltype.Ptr(S)) op = SpaceOperation("malloc", [Constant(S, lltype.Void), Constant({"flavor": "gc"}, lltype.Void)], v) tr = Transformer(FakeCPU(), FakeResidualCallControl()) oplist = tr.rewrite_operation(op) op0, op1 = oplist assert op0.opname == "residual_call_r_r" assert op0.args[0].value == "alloc_with_del" # pseudo-function as a str assert list(op0.args[1]) == [] assert op1.opname == "-live-" assert op1.args == []
def test_unicode_slice(): # test that the oopspec is present and correctly transformed PUNICODE = lltype.Ptr(rstr.UNICODE) INT = lltype.Signed FUNC = lltype.FuncType([PUNICODE, INT, INT], PUNICODE) func = lltype.functionptr(FUNC, "_ll_stringslice", _callable=rstr.LLHelpers._ll_stringslice) v1 = varoftype(PUNICODE) v2 = varoftype(INT) v3 = varoftype(INT) v4 = varoftype(PUNICODE) op = SpaceOperation("direct_call", [const(func), v1, v2, v3], v4) tr = Transformer(FakeCPU(), FakeBuiltinCallControl()) op1 = tr.rewrite_operation(op) assert op1.opname == "residual_call_ir_r" assert op1.args[0].value == func assert op1.args[1] == ListOfKind("int", [v2, v3]) assert op1.args[2] == ListOfKind("ref", [v1]) assert op1.args[3] == "calldescr-%d" % effectinfo.EffectInfo.OS_UNI_SLICE assert op1.result == v4
def test_call_ptr(): def f(x, y, z): return x+y+z FTYPE = lltype.FuncType([lltype.Signed, lltype.Signed, lltype.Signed], lltype.Signed) fptr = lltype.functionptr(FTYPE, "f", _callable=f) def g(x, y, z): tot = 0 tot += fptr(x, y, z) tot += fptr(*(x, y, z)) tot += fptr(x, *(x, z)) return tot res = interpret(g, [1, 2, 4]) assert res == g(1, 2, 4) def wrong(x, y): fptr(*(x, y)) py.test.raises(TypeError, "interpret(wrong, [1, 2])")
def llhelper(F, f): """Gives a low-level function pointer of type F which, when called, invokes the RPython function f(). """ # Example - the following code can be either run or translated: # # def my_rpython_code(): # g = llhelper(F, my_other_rpython_function) # assert typeOf(g) == F # ... # g() # # however the following doesn't translate (xxx could be fixed with hacks): # # prebuilt_g = llhelper(F, f) # def my_rpython_code(): # prebuilt_g() # the next line is the implementation for the purpose of direct running return lltype.functionptr(F.TO, f.func_name, _callable=f)
def test_decode_builtin_call_nomethod(): def myfoobar(i, marker, c): assert marker == 'mymarker' return i * ord(c) myfoobar.oopspec = 'foobar(2, c, i)' TYPE = lltype.FuncType([lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed op = SpaceOperation( 'direct_call', [newconst(fnobj), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'foobar' assert opargs == [newconst(2), vc, vi]
def test_unicode_eq_checknull_char(): # test that the oopspec is present and correctly transformed PUNICODE = lltype.Ptr(rstr.UNICODE) FUNC = lltype.FuncType([PUNICODE, PUNICODE], lltype.Bool) func = lltype.functionptr(FUNC, "ll_streq", _callable=rstr.LLHelpers.ll_streq) v1 = varoftype(PUNICODE) v2 = varoftype(PUNICODE) v3 = varoftype(lltype.Bool) op = SpaceOperation("direct_call", [const(func), v1, v2], v3) cc = FakeBuiltinCallControl() tr = Transformer(FakeCPU(), cc) op1 = tr.rewrite_operation(op) assert op1.opname == "residual_call_r_i" assert op1.args[0].value == func assert op1.args[1] == ListOfKind("ref", [v1, v2]) assert op1.args[2] == "calldescr-%d" % effectinfo.EffectInfo.OS_UNI_EQUAL assert op1.result == v3 # test that the OS_UNIEQ_* functions are registered cic = cc.callinfocollection assert cic.has_oopspec(effectinfo.EffectInfo.OS_UNIEQ_SLICE_NONNULL) assert cic.has_oopspec(effectinfo.EffectInfo.OS_UNIEQ_CHECKNULL_CHAR)
def test_decode_builtin_call_nomethod(): def myfoobar(i, marker, c): assert marker == 'mymarker' return i * ord(c) myfoobar.oopspec = 'foobar(2, c, i)' TYPE = lltype.FuncType([lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed op = SpaceOperation('direct_call', [newconst(fnobj), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'foobar' assert opargs == [newconst(2), vc, vi]
def test_call_ptr(): def f(x, y, z): return x + y + z FTYPE = lltype.FuncType([lltype.Signed, lltype.Signed, lltype.Signed], lltype.Signed) fptr = lltype.functionptr(FTYPE, "f", _callable=f) def g(x, y, z): tot = 0 tot += fptr(x, y, z) tot += fptr(*(x, y, z)) tot += fptr(x, *(x, z)) return tot res = interpret(g, [1, 2, 4]) assert res == g(1, 2, 4) def wrong(x, y): fptr(*(x, y)) py.test.raises(TypeError, "interpret(wrong, [1, 2])")
def test_list_ll_arraycopy(): from rpython.rlib.rgc import ll_arraycopy LIST = lltype.GcArray(lltype.Signed) PLIST = lltype.Ptr(LIST) INT = lltype.Signed FUNC = lltype.FuncType([PLIST] * 2 + [INT] * 3, lltype.Void) func = lltype.functionptr(FUNC, "ll_arraycopy", _callable=ll_arraycopy) v1 = varoftype(PLIST) v2 = varoftype(PLIST) v3 = varoftype(INT) v4 = varoftype(INT) v5 = varoftype(INT) v6 = varoftype(lltype.Void) op = SpaceOperation("direct_call", [const(func), v1, v2, v3, v4, v5], v6) tr = Transformer(FakeCPU(), FakeBuiltinCallControl()) op1 = tr.rewrite_operation(op) assert op1.opname == "residual_call_ir_v" assert op1.args[0].value == func assert op1.args[1] == ListOfKind("int", [v3, v4, v5]) assert op1.args[2] == ListOfKind("ref", [v1, v2]) assert op1.args[3] == "calldescr-%d" % effectinfo.EffectInfo.OS_ARRAYCOPY
def test_unicode_concat(): # test that the oopspec is present and correctly transformed PSTR = lltype.Ptr(rstr.UNICODE) FUNC = lltype.FuncType([PSTR, PSTR], PSTR) func = lltype.functionptr(FUNC, "ll_strconcat", _callable=rstr.LLHelpers.ll_strconcat) v1 = varoftype(PSTR) v2 = varoftype(PSTR) v3 = varoftype(PSTR) op = SpaceOperation("direct_call", [const(func), v1, v2], v3) cc = FakeBuiltinCallControl() tr = Transformer(FakeCPU(), cc) op1 = tr.rewrite_operation(op) assert op1.opname == "residual_call_r_r" assert op1.args[0].value == func assert op1.args[1] == ListOfKind("ref", [v1, v2]) assert op1.args[2] == "calldescr-%d" % effectinfo.EffectInfo.OS_UNI_CONCAT assert op1.result == v3 # # check the callinfo_for_oopspec got = cc.callinfocollection.seen[0] assert got[0] == effectinfo.EffectInfo.OS_UNI_CONCAT assert got[1] == op1.args[2] # the calldescr assert heaptracker.int2adr(got[2]) == llmemory.cast_ptr_to_adr(func)
def constant_func(self, name, inputtypes, rettype, graph, **kwds): FUNC_TYPE = lltype.FuncType(inputtypes, rettype) fn_ptr = lltype.functionptr(FUNC_TYPE, name, graph=graph, **kwds) return Constant(fn_ptr, lltype.Ptr(FUNC_TYPE))
def __getitem__(self, key): F = lltype.FuncType([lltype.Signed, lltype.Signed], lltype.Signed) f = lltype.functionptr(F, key[0]) c_func = Constant(f, lltype.typeOf(f)) return c_func, lltype.Signed
def _transform_hint_close_stack(self, fnptr): # We cannot easily pass variable amount of arguments of the call # across the call to the pypy_asm_stackwalk helper. So we store # them away and restore them. More precisely, we need to # replace 'graph' with code that saves the arguments, and make # a new graph that starts with restoring the arguments. if self._asmgcc_save_restore_arguments is None: self._asmgcc_save_restore_arguments = {} sradict = self._asmgcc_save_restore_arguments sra = [] # list of pointers to raw-malloced containers for args seen = {} FUNC1 = lltype.typeOf(fnptr).TO for TYPE in FUNC1.ARGS: if isinstance(TYPE, lltype.Ptr): TYPE = llmemory.Address num = seen.get(TYPE, 0) seen[TYPE] = num + 1 key = (TYPE, num) if key not in sradict: CONTAINER = lltype.FixedSizeArray(TYPE, 1) p = lltype.malloc(CONTAINER, flavor='raw', zero=True, immortal=True) sradict[key] = Constant(p, lltype.Ptr(CONTAINER)) sra.append(sradict[key]) # # make a copy of the graph that will reload the values graph = fnptr._obj.graph graph2 = copygraph(graph) # # edit the original graph to only store the value of the arguments block = Block(graph.startblock.inputargs) c_item0 = Constant('item0', lltype.Void) assert len(block.inputargs) == len(sra) for v_arg, c_p in zip(block.inputargs, sra): if isinstance(v_arg.concretetype, lltype.Ptr): v_adr = varoftype(llmemory.Address) block.operations.append( SpaceOperation("cast_ptr_to_adr", [v_arg], v_adr)) v_arg = v_adr v_void = varoftype(lltype.Void) block.operations.append( SpaceOperation("bare_setfield", [c_p, c_item0, v_arg], v_void)) # # call asm_stackwalk(graph2) FUNC2 = lltype.FuncType([], FUNC1.RESULT) fnptr2 = lltype.functionptr(FUNC2, fnptr._obj._name + '_reload', graph=graph2) c_fnptr2 = Constant(fnptr2, lltype.Ptr(FUNC2)) HELPERFUNC = lltype.FuncType([lltype.Ptr(FUNC2), ASM_FRAMEDATA_HEAD_PTR], FUNC1.RESULT) v_asm_stackwalk = varoftype(lltype.Ptr(HELPERFUNC), "asm_stackwalk") block.operations.append( SpaceOperation("cast_pointer", [c_asm_stackwalk], v_asm_stackwalk)) v_result = varoftype(FUNC1.RESULT) block.operations.append( SpaceOperation("indirect_call", [v_asm_stackwalk, c_fnptr2, c_gcrootanchor, Constant(None, lltype.Void)], v_result)) block.closeblock(Link([v_result], graph.returnblock)) graph.startblock = block # # edit the copy of the graph to reload the values block2 = graph2.startblock block1 = Block([]) reloadedvars = [] for v, c_p in zip(block2.inputargs, sra): v = v.copy() if isinstance(v.concretetype, lltype.Ptr): w = varoftype(llmemory.Address) else: w = v block1.operations.append(SpaceOperation('getfield', [c_p, c_item0], w)) if w is not v: block1.operations.append(SpaceOperation('cast_adr_to_ptr', [w], v)) reloadedvars.append(v) block1.closeblock(Link(reloadedvars, block2)) graph2.startblock = block1 # checkgraph(graph) checkgraph(graph2)
def getcallable(graph): F = lltype.FuncType([], lltype.Signed) return lltype.functionptr(F, 'bar')
def _transform_hint_close_stack(self, fnptr): # We cannot easily pass variable amount of arguments of the call # across the call to the pypy_asm_stackwalk helper. So we store # them away and restore them. More precisely, we need to # replace 'graph' with code that saves the arguments, and make # a new graph that starts with restoring the arguments. if self._asmgcc_save_restore_arguments is None: self._asmgcc_save_restore_arguments = {} sradict = self._asmgcc_save_restore_arguments sra = [] # list of pointers to raw-malloced containers for args seen = {} FUNC1 = lltype.typeOf(fnptr).TO for TYPE in FUNC1.ARGS: if isinstance(TYPE, lltype.Ptr): TYPE = llmemory.Address num = seen.get(TYPE, 0) seen[TYPE] = num + 1 key = (TYPE, num) if key not in sradict: CONTAINER = lltype.FixedSizeArray(TYPE, 1) p = lltype.malloc(CONTAINER, flavor='raw', zero=True, immortal=True) sradict[key] = Constant(p, lltype.Ptr(CONTAINER)) sra.append(sradict[key]) # # make a copy of the graph that will reload the values graph = fnptr._obj.graph graph2 = copygraph(graph) # # edit the original graph to only store the value of the arguments block = Block(graph.startblock.inputargs) c_item0 = Constant('item0', lltype.Void) assert len(block.inputargs) == len(sra) for v_arg, c_p in zip(block.inputargs, sra): if isinstance(v_arg.concretetype, lltype.Ptr): v_adr = varoftype(llmemory.Address) block.operations.append( SpaceOperation("cast_ptr_to_adr", [v_arg], v_adr)) v_arg = v_adr v_void = varoftype(lltype.Void) block.operations.append( SpaceOperation("bare_setfield", [c_p, c_item0, v_arg], v_void)) # # call asm_stackwalk(graph2) FUNC2 = lltype.FuncType([], FUNC1.RESULT) fnptr2 = lltype.functionptr(FUNC2, fnptr._obj._name + '_reload', graph=graph2) c_fnptr2 = Constant(fnptr2, lltype.Ptr(FUNC2)) HELPERFUNC = lltype.FuncType( [lltype.Ptr(FUNC2), ASM_FRAMEDATA_HEAD_PTR], FUNC1.RESULT) v_asm_stackwalk = varoftype(lltype.Ptr(HELPERFUNC), "asm_stackwalk") block.operations.append( SpaceOperation("cast_pointer", [c_asm_stackwalk], v_asm_stackwalk)) v_result = varoftype(FUNC1.RESULT) block.operations.append( SpaceOperation("indirect_call", [ v_asm_stackwalk, c_fnptr2, c_gcrootanchor, Constant(None, lltype.Void) ], v_result)) block.closeblock(Link([v_result], graph.returnblock)) graph.startblock = block # # edit the copy of the graph to reload the values block2 = graph2.startblock block1 = Block([]) reloadedvars = [] for v, c_p in zip(block2.inputargs, sra): v = v.copy() if isinstance(v.concretetype, lltype.Ptr): w = varoftype(llmemory.Address) else: w = v block1.operations.append( SpaceOperation('getfield', [c_p, c_item0], w)) if w is not v: block1.operations.append( SpaceOperation('cast_adr_to_ptr', [w], v)) reloadedvars.append(v) block1.closeblock(Link(reloadedvars, block2)) graph2.startblock = block1 # checkgraph(graph) checkgraph(graph2)
def llexternal(name, args, result, _callable=None, compilation_info=ExternalCompilationInfo(), sandboxsafe=False, releasegil='auto', _nowrapper=False, calling_conv='c', 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). releasegil: 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 = {} has_callback = False for ARG in args: if _isfunctype(ARG): has_callback = True if has_callback: kwds['_callbacks'] = callbackholder = CallbackHolder() else: callbackholder = None if releasegil in (False, True): # invoke the around-handlers, which release the GIL, if and only if # the C function is thread-safe. invoke_around_handlers = releasegil 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). # Also, _nowrapper functions cannot release the GIL, by default. invoke_around_handlers = not sandboxsafe and not _nowrapper 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 assert not (elidable_function and random_effects_on_gcobjs) 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._call_aroundstate_target_ = funcptr 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): 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_ = 'try' # 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
def llexternal(name, args, result, _callable=None, compilation_info=ExternalCompilationInfo(), sandboxsafe=False, releasegil='auto', _nowrapper=False, calling_conv='c', elidable_function=False, macro=None, random_effects_on_gcobjs='auto', save_err=RFFI_ERR_NONE): """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). releasegil: 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) else: assert macro is None, "'macro' is useless if you specify '_callable'" if elidable_function: _callable._elidable_function_ = True kwds = {} has_callback = False for ARG in args: if _isfunctype(ARG): has_callback = True if has_callback: kwds['_callbacks'] = callbackholder = CallbackHolder() else: callbackholder = None if releasegil in (False, True): # invoke the around-handlers, which release the GIL, if and only if # the C function is thread-safe. invoke_around_handlers = releasegil 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). # Also, _nowrapper functions cannot release the GIL, by default. invoke_around_handlers = not sandboxsafe and not _nowrapper 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 assert not (elidable_function and random_effects_on_gcobjs) 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: assert save_err == RFFI_ERR_NONE 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(""" from rpython.rlib import rgil def call_external_function(%(argnames)s): rgil.release() # NB. it is essential that no exception checking occurs here! if %(save_err)d: from rpython.rlib import rposix rposix._errno_before(%(save_err)d) res = funcptr(%(argnames)s) if %(save_err)d: from rpython.rlib import rposix rposix._errno_after(%(save_err)d) rgil.acquire() return res """ % locals()) miniglobals = {'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_aroundstate_target_' is used by the JIT to generate a # CALL_RELEASE_GIL directly to 'funcptr'. This doesn't work if # 'funcptr' might be a C macro, though. if macro is None: call_external_function._call_aroundstate_target_ = funcptr, save_err # 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 GIL handling, we can just call # the low-level function pointer carelessly if macro is None and save_err == RFFI_ERR_NONE: call_external_function = funcptr else: # ...well, unless it's a macro, in which case we still have # to hide it from the JIT... argnames = ', '.join(['a%d' % i for i in range(len(args))]) source = py.code.Source(""" def call_external_function(%(argnames)s): if %(save_err)d: from rpython.rlib import rposix rposix._errno_before(%(save_err)d) res = funcptr(%(argnames)s) if %(save_err)d: from rpython.rlib import rposix rposix._errno_after(%(save_err)d) return res """ % locals()) miniglobals = {'funcptr': funcptr, '__name__': __name__, } exec source.compile() in miniglobals call_external_function = miniglobals['call_external_function'] call_external_function = func_with_new_name(call_external_function, 'ccall_' + name) call_external_function = jit.dont_look_inside( call_external_function) unrolling_arg_tps = unrolling_iterable(enumerate(args)) def wrapper(*args): 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 use_gil = invoke_around_handlers arg = llhelper(TARGET, _make_wrapper_for(TARGET, arg, callbackholder, use_gil)) 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_ = 'try' # 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
def _run(self, atypes, rtype, avalues, rvalue, expected_call_release_gil=1, supports_floats=True, supports_longlong=False, supports_singlefloats=False): cif_description = get_description(atypes, rtype) def verify(*args): for a, exp_a in zip(args, avalues): if (lltype.typeOf(exp_a) == rffi.ULONG and lltype.typeOf(a) == lltype.Signed): a = rffi.cast(rffi.ULONG, a) assert a == exp_a return rvalue FUNC = lltype.FuncType([lltype.typeOf(avalue) for avalue in avalues], lltype.typeOf(rvalue)) func = lltype.functionptr(FUNC, 'verify', _callable=verify) func_addr = rffi.cast(rffi.VOIDP, func) for i in range(len(avalues)): cif_description.exchange_args[i] = (i + 1) * 16 cif_description.exchange_result = (len(avalues) + 1) * 16 unroll_avalues = unrolling_iterable(avalues) def fake_call_impl_any(cif_description, func_addr, exchange_buffer): ofs = 16 for avalue in unroll_avalues: TYPE = rffi.CArray(lltype.typeOf(avalue)) data = rffi.ptradd(exchange_buffer, ofs) got = rffi.cast(lltype.Ptr(TYPE), data)[0] if lltype.typeOf(avalue) is lltype.SingleFloat: got = float(got) avalue = float(avalue) elif (lltype.typeOf(avalue) is rffi.SIGNEDCHAR or lltype.typeOf(avalue) is rffi.UCHAR): got = intmask(got) avalue = intmask(avalue) assert got == avalue ofs += 16 if rvalue is not None: write_rvalue = rvalue else: write_rvalue = 12923 # ignored TYPE = rffi.CArray(lltype.typeOf(write_rvalue)) data = rffi.ptradd(exchange_buffer, ofs) rffi.cast(lltype.Ptr(TYPE), data)[0] = write_rvalue def f(i): exbuf = lltype.malloc(rffi.CCHARP.TO, (len(avalues) + 2) * 16, flavor='raw') targetptr = rffi.ptradd(exbuf, 16) for avalue in unroll_avalues: TYPE = rffi.CArray(lltype.typeOf(avalue)) if i >= 9: # a guard that can fail pass rffi.cast(lltype.Ptr(TYPE), targetptr)[0] = avalue targetptr = rffi.ptradd(targetptr, 16) jit_ffi_call(cif_description, func_addr, exbuf) if rvalue is None: res = 654321 else: TYPE = rffi.CArray(lltype.typeOf(rvalue)) res = rffi.cast(lltype.Ptr(TYPE), targetptr)[0] lltype.free(exbuf, flavor='raw') if lltype.typeOf(res) is lltype.SingleFloat: res = float(res) return res def matching_result(res, rvalue): if rvalue is None: return res == 654321 if isinstance(rvalue, r_singlefloat): rvalue = float(rvalue) if lltype.typeOf(rvalue) is rffi.ULONG: res = intmask(res) rvalue = intmask(rvalue) return res == rvalue with FakeFFI(fake_call_impl_any): res = f(-42) assert matching_result(res, rvalue) res = self.interp_operations( f, [-42], supports_floats=supports_floats, supports_longlong=supports_longlong, supports_singlefloats=supports_singlefloats) if is_longlong(FUNC.RESULT): # longlongs are returned as floats, but that's just # an inconvenience of interp_operations(). Normally both # longlong and floats are passed around as longlongs. res = float2longlong(res) assert matching_result(res, rvalue) self.check_operations_history( call_may_force=0, call_release_gil=expected_call_release_gil) ################################################## driver = jit.JitDriver(reds=['i'], greens=[]) def main(): i = 0 while 1: driver.jit_merge_point(i=i) res = f(i) i += 1 if i == 12: return res self.meta_interp(main, [])
def _run(self, atypes, rtype, avalues, rvalue, expected_call_release_gil=1, supports_floats=True, supports_longlong=False, supports_singlefloats=False): cif_description = get_description(atypes, rtype) def verify(*args): for a, exp_a in zip(args, avalues): if (lltype.typeOf(exp_a) == rffi.ULONG and lltype.typeOf(a) == lltype.Signed): a = rffi.cast(rffi.ULONG, a) assert a == exp_a return rvalue FUNC = lltype.FuncType([lltype.typeOf(avalue) for avalue in avalues], lltype.typeOf(rvalue)) func = lltype.functionptr(FUNC, 'verify', _callable=verify) func_addr = rffi.cast(rffi.VOIDP, func) for i in range(len(avalues)): cif_description.exchange_args[i] = (i+1) * 16 cif_description.exchange_result = (len(avalues)+1) * 16 unroll_avalues = unrolling_iterable(avalues) def fake_call_impl_any(cif_description, func_addr, exchange_buffer): ofs = 16 for avalue in unroll_avalues: TYPE = rffi.CArray(lltype.typeOf(avalue)) data = rffi.ptradd(exchange_buffer, ofs) got = rffi.cast(lltype.Ptr(TYPE), data)[0] if lltype.typeOf(avalue) is lltype.SingleFloat: got = float(got) avalue = float(avalue) elif (lltype.typeOf(avalue) is rffi.SIGNEDCHAR or lltype.typeOf(avalue) is rffi.UCHAR): got = intmask(got) avalue = intmask(avalue) assert got == avalue ofs += 16 if rvalue is not None: write_rvalue = rvalue else: write_rvalue = 12923 # ignored TYPE = rffi.CArray(lltype.typeOf(write_rvalue)) data = rffi.ptradd(exchange_buffer, ofs) rffi.cast(lltype.Ptr(TYPE), data)[0] = write_rvalue def f(i): exbuf = lltype.malloc(rffi.CCHARP.TO, (len(avalues)+2) * 16, flavor='raw') targetptr = rffi.ptradd(exbuf, 16) for avalue in unroll_avalues: TYPE = rffi.CArray(lltype.typeOf(avalue)) if i >= 9: # a guard that can fail pass rffi.cast(lltype.Ptr(TYPE), targetptr)[0] = avalue targetptr = rffi.ptradd(targetptr, 16) jit_ffi_call(cif_description, func_addr, exbuf) if rvalue is None: res = 654321 else: TYPE = rffi.CArray(lltype.typeOf(rvalue)) res = rffi.cast(lltype.Ptr(TYPE), targetptr)[0] lltype.free(exbuf, flavor='raw') if lltype.typeOf(res) is lltype.SingleFloat: res = float(res) return res def matching_result(res, rvalue): if rvalue is None: return res == 654321 if isinstance(rvalue, r_singlefloat): rvalue = float(rvalue) if lltype.typeOf(rvalue) is rffi.ULONG: res = intmask(res) rvalue = intmask(rvalue) return res == rvalue with FakeFFI(fake_call_impl_any): res = f(-42) assert matching_result(res, rvalue) res = self.interp_operations(f, [-42], supports_floats = supports_floats, supports_longlong = supports_longlong, supports_singlefloats = supports_singlefloats) if is_longlong(FUNC.RESULT): # longlongs are returned as floats, but that's just # an inconvenience of interp_operations(). Normally both # longlong and floats are passed around as longlongs. res = float2longlong(res) assert matching_result(res, rvalue) self.check_operations_history(call_may_force=0, call_release_gil=expected_call_release_gil) ################################################## driver = jit.JitDriver(reds=['i'], greens=[]) def main(): i = 0 while 1: driver.jit_merge_point(i=i) res = f(i) i += 1 if i == 12: return res self.meta_interp(main, [])
def getfunctionptr(graph): F = lltype.FuncType([], lltype.Signed) return lltype.functionptr(F, 'bar')
def getexternalcallable(self, ll_args, ll_result, name, **kwds): FT = lltype.FuncType(ll_args, ll_result) return lltype.functionptr(FT, name, **kwds)