PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/rpython/jit/metainterp/test/test_fficall.py

https://bitbucket.org/pypy/pypy/
Python | 387 lines | 384 code | 2 blank | 1 comment | 1 complexity | 4451b5c2f283862357550011c2803d28 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. import py
  2. from _pytest.monkeypatch import monkeypatch
  3. import sys
  4. import ctypes, math
  5. from rpython.rtyper.lltypesystem import lltype, rffi
  6. from rpython.rtyper.annlowlevel import llhelper
  7. from rpython.jit.metainterp.test.support import LLJitMixin
  8. from rpython.jit.codewriter.longlong import is_longlong, is_64_bit
  9. from rpython.rlib import jit
  10. from rpython.rlib import jit_libffi
  11. from rpython.rlib.jit_libffi import (types, CIF_DESCRIPTION, FFI_TYPE_PP,
  12. jit_ffi_call)
  13. from rpython.rlib.unroll import unrolling_iterable
  14. from rpython.rlib.rarithmetic import intmask, r_longlong, r_singlefloat, r_uint
  15. from rpython.rlib.longlong2float import float2longlong
  16. def get_description(atypes, rtype):
  17. p = lltype.malloc(CIF_DESCRIPTION, len(atypes),
  18. flavor='raw', immortal=True)
  19. p.abi = 1 # default
  20. p.nargs = len(atypes)
  21. p.rtype = rtype
  22. p.atypes = lltype.malloc(FFI_TYPE_PP.TO, len(atypes),
  23. flavor='raw', immortal=True)
  24. for i in range(len(atypes)):
  25. p.atypes[i] = atypes[i]
  26. return p
  27. class FakeFFI(object):
  28. """
  29. Context manager to monkey patch jit_libffi with our custom "libffi-like"
  30. function
  31. """
  32. def __init__(self, fake_call_impl_any):
  33. self.fake_call_impl_any = fake_call_impl_any
  34. self.monkey = monkeypatch()
  35. def __enter__(self, *args):
  36. self.monkey.setattr(jit_libffi, 'jit_ffi_call_impl_any', self.fake_call_impl_any)
  37. def __exit__(self, *args):
  38. self.monkey.undo()
  39. class FfiCallTests(object):
  40. def _run(self, atypes, rtype, avalues, rvalue,
  41. expected_call_release_gil_i=1,
  42. expected_call_release_gil_f=0,
  43. expected_call_release_gil_n=0,
  44. expected_call_may_force_f=0,
  45. supports_floats=True,
  46. supports_longlong=False,
  47. supports_singlefloats=False):
  48. cif_description = get_description(atypes, rtype)
  49. def verify(*args):
  50. for a, exp_a in zip(args, avalues):
  51. if (lltype.typeOf(exp_a) == rffi.ULONG and
  52. lltype.typeOf(a) == lltype.Signed):
  53. a = rffi.cast(rffi.ULONG, a)
  54. assert a == exp_a
  55. return rvalue
  56. FUNC = lltype.FuncType([lltype.typeOf(avalue) for avalue in avalues],
  57. lltype.typeOf(rvalue))
  58. func = lltype.functionptr(FUNC, 'verify', _callable=verify)
  59. func_addr = rffi.cast(rffi.VOIDP, func)
  60. for i in range(len(avalues)):
  61. cif_description.exchange_args[i] = (i+1) * 16
  62. cif_description.exchange_result = (len(avalues)+1) * 16
  63. unroll_avalues = unrolling_iterable(avalues)
  64. BIG_ENDIAN = (sys.byteorder == 'big')
  65. def fake_call_impl_any(cif_description, func_addr, exchange_buffer):
  66. ofs = 16
  67. for avalue in unroll_avalues:
  68. TYPE = rffi.CArray(lltype.typeOf(avalue))
  69. data = rffi.ptradd(exchange_buffer, ofs)
  70. got = rffi.cast(lltype.Ptr(TYPE), data)[0]
  71. if lltype.typeOf(avalue) is lltype.SingleFloat:
  72. got = float(got)
  73. avalue = float(avalue)
  74. elif (lltype.typeOf(avalue) is rffi.SIGNEDCHAR or
  75. lltype.typeOf(avalue) is rffi.UCHAR):
  76. got = intmask(got)
  77. avalue = intmask(avalue)
  78. assert got == avalue
  79. ofs += 16
  80. write_to_ofs = 0
  81. if rvalue is not None:
  82. write_rvalue = rvalue
  83. if BIG_ENDIAN:
  84. if (lltype.typeOf(write_rvalue) is rffi.SIGNEDCHAR or
  85. lltype.typeOf(write_rvalue) is rffi.UCHAR):
  86. # 'write_rvalue' is an int type smaller than Signed
  87. write_to_ofs = rffi.sizeof(rffi.LONG) - 1
  88. else:
  89. write_rvalue = 12923 # ignored
  90. TYPE = rffi.CArray(lltype.typeOf(write_rvalue))
  91. data = rffi.ptradd(exchange_buffer, ofs)
  92. rffi.cast(lltype.Ptr(TYPE), data)[write_to_ofs] = write_rvalue
  93. def f(i):
  94. exbuf = lltype.malloc(rffi.CCHARP.TO, (len(avalues)+2) * 16,
  95. flavor='raw')
  96. targetptr = rffi.ptradd(exbuf, 16)
  97. for avalue in unroll_avalues:
  98. TYPE = rffi.CArray(lltype.typeOf(avalue))
  99. if i >= 9: # a guard that can fail
  100. pass
  101. rffi.cast(lltype.Ptr(TYPE), targetptr)[0] = avalue
  102. targetptr = rffi.ptradd(targetptr, 16)
  103. jit_ffi_call(cif_description, func_addr, exbuf)
  104. if rvalue is None:
  105. res = 654321
  106. else:
  107. TYPE = rffi.CArray(lltype.typeOf(rvalue))
  108. res = rffi.cast(lltype.Ptr(TYPE), targetptr)[0]
  109. lltype.free(exbuf, flavor='raw')
  110. if lltype.typeOf(res) is lltype.SingleFloat:
  111. res = float(res)
  112. return res
  113. def matching_result(res, rvalue):
  114. if rvalue is None:
  115. return res == 654321
  116. if isinstance(rvalue, r_singlefloat):
  117. rvalue = float(rvalue)
  118. if lltype.typeOf(rvalue) is rffi.ULONG:
  119. res = intmask(res)
  120. rvalue = intmask(rvalue)
  121. return res == rvalue
  122. with FakeFFI(fake_call_impl_any):
  123. res = f(-42)
  124. assert matching_result(res, rvalue)
  125. res = self.interp_operations(f, [-42],
  126. supports_floats = supports_floats,
  127. supports_longlong = supports_longlong,
  128. supports_singlefloats = supports_singlefloats)
  129. if is_longlong(FUNC.RESULT):
  130. # longlongs are returned as floats, but that's just
  131. # an inconvenience of interp_operations(). Normally both
  132. # longlong and floats are passed around as longlongs.
  133. res = float2longlong(res)
  134. assert matching_result(res, rvalue)
  135. self.check_operations_history(call_may_force_i=0,
  136. call_may_force_f=expected_call_may_force_f,
  137. call_may_force_n=0,
  138. call_release_gil_i=expected_call_release_gil_i,
  139. call_release_gil_f=expected_call_release_gil_f,
  140. call_release_gil_n=expected_call_release_gil_n)
  141. ##################################################
  142. driver = jit.JitDriver(reds=['i'], greens=[])
  143. def main():
  144. i = 0
  145. while 1:
  146. driver.jit_merge_point(i=i)
  147. res = f(i)
  148. i += 1
  149. if i == 12:
  150. return res
  151. self.meta_interp(main, [])
  152. def test_simple_call_int(self):
  153. self._run([types.signed] * 2, types.signed, [456, 789], -42)
  154. def test_many_arguments(self):
  155. for i in [0, 6, 20]:
  156. self._run([types.signed] * i, types.signed,
  157. [-123456*j for j in range(i)],
  158. -42434445)
  159. def test_simple_call_float(self, **kwds):
  160. kwds.setdefault('supports_floats', True)
  161. kwds['expected_call_release_gil_f'] = kwds.pop('expected_call_release_gil', 1)
  162. kwds['expected_call_release_gil_i'] = 0
  163. self._run([types.double] * 2, types.double, [45.6, 78.9], -4.2, **kwds)
  164. def test_simple_call_longlong(self, **kwds):
  165. kwds.setdefault('supports_longlong', True)
  166. if is_64_bit:
  167. kwds['expected_call_release_gil_i'] = kwds.pop('expected_call_release_gil', 1)
  168. else:
  169. kwds['expected_call_release_gil_f'] = kwds.pop('expected_call_release_gil', 1)
  170. kwds['expected_call_release_gil_i'] = 0
  171. maxint32 = 2147483647
  172. a = r_longlong(maxint32) + 1
  173. b = r_longlong(maxint32) + 2
  174. self._run([types.slonglong] * 2, types.slonglong, [a, b], a, **kwds)
  175. def test_simple_call_singlefloat_args(self, **kwds):
  176. kwds.setdefault('supports_singlefloats', True)
  177. kwds['expected_call_release_gil_f'] = kwds.pop('expected_call_release_gil', 1)
  178. kwds['expected_call_release_gil_i'] = 0
  179. self._run([types.float] * 2, types.double,
  180. [r_singlefloat(10.5), r_singlefloat(31.5)],
  181. -4.5, **kwds)
  182. def test_simple_call_singlefloat(self, **kwds):
  183. kwds.setdefault('supports_singlefloats', True)
  184. kwds['expected_call_release_gil_i'] = kwds.pop('expected_call_release_gil', 1)
  185. self._run([types.float] * 2, types.float,
  186. [r_singlefloat(10.5), r_singlefloat(31.5)],
  187. r_singlefloat(-4.5), **kwds)
  188. def test_simple_call_longdouble(self):
  189. # longdouble is not supported, so we expect NOT to generate a call_release_gil
  190. self._run([types.longdouble] * 2, types.longdouble, [12.3, 45.6], 78.9,
  191. expected_call_release_gil_i=0, expected_call_release_gil_f=0,
  192. )
  193. def test_returns_none(self):
  194. self._run([types.signed] * 2, types.void, [456, 789], None,
  195. expected_call_release_gil_i=0, expected_call_release_gil_n=1)
  196. def test_returns_signedchar(self):
  197. self._run([types.sint8], types.sint8,
  198. [rffi.cast(rffi.SIGNEDCHAR, -28)],
  199. rffi.cast(rffi.SIGNEDCHAR, -42))
  200. def test_handle_unsigned(self):
  201. self._run([types.ulong], types.ulong,
  202. [rffi.cast(rffi.ULONG, r_uint(sys.maxint + 91348))],
  203. rffi.cast(rffi.ULONG, r_uint(sys.maxint + 4242)))
  204. def test_handle_unsignedchar(self):
  205. self._run([types.uint8], types.uint8,
  206. [rffi.cast(rffi.UCHAR, 191)],
  207. rffi.cast(rffi.UCHAR, 180))
  208. def _add_libffi_types_to_ll2types_maybe(self):
  209. # not necessary on the llgraph backend, but needed for x86.
  210. # see rpython/jit/backend/x86/test/test_fficall.py
  211. pass
  212. def test_guard_not_forced_fails(self):
  213. self._add_libffi_types_to_ll2types_maybe()
  214. FUNC = lltype.FuncType([lltype.Signed], lltype.Signed)
  215. cif_description = get_description([types.slong], types.slong)
  216. cif_description.exchange_args[0] = 16
  217. cif_description.exchange_result = 32
  218. ARRAY = lltype.Ptr(rffi.CArray(lltype.Signed))
  219. @jit.dont_look_inside
  220. def fn(n):
  221. if n >= 50:
  222. exctx.m = exctx.topframeref().n # forces the frame
  223. return n*2
  224. # this function simulates what a real libffi_call does: reading from
  225. # the buffer, calling a function (which can potentially call callbacks
  226. # and force frames) and write back to the buffer
  227. def fake_call_impl_any(cif_description, func_addr, exchange_buffer):
  228. # read the args from the buffer
  229. data_in = rffi.ptradd(exchange_buffer, 16)
  230. n = rffi.cast(ARRAY, data_in)[0]
  231. #
  232. # logic of the function
  233. func_ptr = rffi.cast(lltype.Ptr(FUNC), func_addr)
  234. n = func_ptr(n)
  235. #
  236. # write the result to the buffer
  237. data_out = rffi.ptradd(exchange_buffer, 32)
  238. rffi.cast(ARRAY, data_out)[0] = n
  239. def do_call(n):
  240. func_ptr = llhelper(lltype.Ptr(FUNC), fn)
  241. exbuf = lltype.malloc(rffi.CCHARP.TO, 48, flavor='raw', zero=True)
  242. data_in = rffi.ptradd(exbuf, 16)
  243. rffi.cast(ARRAY, data_in)[0] = n
  244. jit_ffi_call(cif_description, func_ptr, exbuf)
  245. data_out = rffi.ptradd(exbuf, 32)
  246. res = rffi.cast(ARRAY, data_out)[0]
  247. lltype.free(exbuf, flavor='raw')
  248. return res
  249. #
  250. #
  251. class XY:
  252. pass
  253. class ExCtx:
  254. pass
  255. exctx = ExCtx()
  256. myjitdriver = jit.JitDriver(greens = [], reds = ['n'])
  257. def f():
  258. n = 0
  259. while n < 100:
  260. myjitdriver.jit_merge_point(n=n)
  261. xy = XY()
  262. xy.n = n
  263. exctx.topframeref = vref = jit.virtual_ref(xy)
  264. res = do_call(n) # this is equivalent of a cffi call which
  265. # sometimes forces a frame
  266. # when n==50, fn() will force the frame, so guard_not_forced
  267. # fails and we enter blackholing: this test makes sure that
  268. # the result of call_release_gil is kept alive before the
  269. # raw_store, and that the corresponding box is passed
  270. # in the fail_args. Before the fix, the result of
  271. # call_release_gil was simply lost and when guard_not_forced
  272. # failed, and the value of "res" was unpredictable.
  273. # See commit b84ff38f34bd and subsequents.
  274. assert res == n*2
  275. jit.virtual_ref_finish(vref, xy)
  276. exctx.topframeref = jit.vref_None
  277. n += 1
  278. return n
  279. with FakeFFI(fake_call_impl_any):
  280. assert f() == 100
  281. res = self.meta_interp(f, [])
  282. assert res == 100
  283. class TestFfiCall(FfiCallTests, LLJitMixin):
  284. def test_jit_ffi_vref(self):
  285. py.test.skip("unsupported so far")
  286. from rpython.rlib import clibffi
  287. from rpython.rlib.jit_libffi import jit_ffi_prep_cif, jit_ffi_call
  288. math_sin = intmask(ctypes.cast(ctypes.CDLL(None).sin,
  289. ctypes.c_void_p).value)
  290. math_sin = rffi.cast(rffi.VOIDP, math_sin)
  291. cd = lltype.malloc(CIF_DESCRIPTION, 1, flavor='raw')
  292. cd.abi = clibffi.FFI_DEFAULT_ABI
  293. cd.nargs = 1
  294. cd.rtype = clibffi.cast_type_to_ffitype(rffi.DOUBLE)
  295. atypes = lltype.malloc(clibffi.FFI_TYPE_PP.TO, 1, flavor='raw')
  296. atypes[0] = clibffi.cast_type_to_ffitype(rffi.DOUBLE)
  297. cd.atypes = atypes
  298. cd.exchange_size = 64 # 64 bytes of exchange data
  299. cd.exchange_result = 24
  300. cd.exchange_args[0] = 16
  301. def f():
  302. #
  303. jit_ffi_prep_cif(cd)
  304. #
  305. assert rffi.sizeof(rffi.DOUBLE) == 8
  306. exb = lltype.malloc(rffi.DOUBLEP.TO, 8, flavor='raw')
  307. exb[2] = 1.23
  308. jit_ffi_call(cd, math_sin, rffi.cast(rffi.CCHARP, exb))
  309. res = exb[3]
  310. lltype.free(exb, flavor='raw')
  311. #
  312. return res
  313. #
  314. res = self.interp_operations(f, [])
  315. lltype.free(cd, flavor='raw')
  316. assert res == math.sin(1.23)
  317. lltype.free(atypes, flavor='raw')
  318. def test_simple_call_float_unsupported(self):
  319. self.test_simple_call_float(supports_floats=False,
  320. expected_call_release_gil=0)
  321. def test_simple_call_longlong_unsupported(self):
  322. self.test_simple_call_longlong(supports_longlong=False,
  323. expected_call_release_gil=is_64_bit)
  324. def test_simple_call_singlefloat_unsupported(self):
  325. self.test_simple_call_singlefloat(supports_singlefloats=False,
  326. expected_call_release_gil=0)
  327. def test_calldescrof_dynamic_returning_none(self):
  328. from rpython.jit.backend.llgraph.runner import LLGraphCPU
  329. old = LLGraphCPU.calldescrof_dynamic
  330. try:
  331. LLGraphCPU.calldescrof_dynamic = lambda *args: None
  332. self.test_simple_call_float(expected_call_release_gil=0,
  333. expected_call_may_force_f=1)
  334. finally:
  335. LLGraphCPU.calldescrof_dynamic = old