PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/rpython/translator/exceptiontransform.py

https://bitbucket.org/pypy/pypy/
Python | 523 lines | 417 code | 67 blank | 39 comment | 58 complexity | ec9fb640ddbe8dcc931fc175ad21ffe3 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. from rpython.translator.simplify import join_blocks, cleanup_graph
  2. from rpython.translator.unsimplify import varoftype
  3. from rpython.translator.unsimplify import insert_empty_block, split_block
  4. from rpython.translator.backendopt import canraise, inline
  5. from rpython.flowspace.model import Block, Constant, Variable, Link, \
  6. SpaceOperation, FunctionGraph, mkentrymap
  7. from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
  8. from rpython.rtyper.lltypesystem import lloperation
  9. from rpython.rtyper.rclass import ll_inst_type
  10. from rpython.rtyper import rtyper
  11. from rpython.rtyper.rmodel import inputconst
  12. from rpython.rlib.rarithmetic import r_uint, r_longlong, r_ulonglong
  13. from rpython.rlib.rarithmetic import r_singlefloat, r_longfloat
  14. from rpython.rlib.debug import ll_assert
  15. from rpython.rtyper.llannotation import lltype_to_annotation
  16. from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator
  17. from rpython.tool.sourcetools import func_with_new_name
  18. PrimitiveErrorValue = {lltype.Signed: -1,
  19. lltype.Unsigned: r_uint(-1),
  20. lltype.SignedLongLong: r_longlong(-1),
  21. lltype.UnsignedLongLong: r_ulonglong(-1),
  22. lltype.Float: -1.0,
  23. lltype.SingleFloat: r_singlefloat(-1.0),
  24. lltype.LongFloat: r_longfloat(-1.0),
  25. lltype.Char: chr(255),
  26. lltype.UniChar: unichr(0xFFFF), # XXX is this always right?
  27. lltype.Bool: True,
  28. llmemory.Address: llmemory.NULL,
  29. lltype.Void: None}
  30. for TYPE in rffi.NUMBER_TYPES:
  31. PrimitiveErrorValue[TYPE] = lltype.cast_primitive(TYPE, -1)
  32. del TYPE
  33. def error_value(T):
  34. if isinstance(T, lltype.Primitive):
  35. return PrimitiveErrorValue[T]
  36. elif isinstance(T, lltype.Ptr):
  37. return lltype.nullptr(T.TO)
  38. assert 0, "not implemented yet"
  39. def error_constant(T):
  40. return Constant(error_value(T), T)
  41. def constant_value(llvalue):
  42. return Constant(llvalue, lltype.typeOf(llvalue))
  43. class ExceptionTransformer(object):
  44. def __init__(self, translator):
  45. self.translator = translator
  46. self.raise_analyzer = canraise.RaiseAnalyzer(translator)
  47. edata = translator.rtyper.exceptiondata
  48. self.lltype_of_exception_value = edata.lltype_of_exception_value
  49. self.lltype_of_exception_type = edata.lltype_of_exception_type
  50. self.mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper)
  51. exc_data, null_type, null_value = self.setup_excdata()
  52. (assertion_error_ll_exc_type,
  53. assertion_error_ll_exc) = self.get_builtin_exception(AssertionError)
  54. (n_i_error_ll_exc_type,
  55. n_i_error_ll_exc) = self.get_builtin_exception(NotImplementedError)
  56. self.c_assertion_error_ll_exc_type = constant_value(
  57. assertion_error_ll_exc_type)
  58. self.c_n_i_error_ll_exc_type = constant_value(n_i_error_ll_exc_type)
  59. def rpyexc_occured():
  60. exc_type = exc_data.exc_type
  61. return bool(exc_type)
  62. def rpyexc_fetch_type():
  63. return exc_data.exc_type
  64. def rpyexc_fetch_value():
  65. return exc_data.exc_value
  66. def rpyexc_clear():
  67. exc_data.exc_type = null_type
  68. exc_data.exc_value = null_value
  69. def rpyexc_raise(etype, evalue):
  70. # When compiling in debug mode, the following ll_asserts will
  71. # crash the program as soon as it raises AssertionError or
  72. # NotImplementedError. Useful when you are in a debugger.
  73. # When compiling in release mode, AssertionErrors and
  74. # NotImplementedErrors are raised normally, and only later
  75. # caught by debug_catch_exception and printed, which allows
  76. # us to see at least part of the traceback for them.
  77. ll_assert(etype != assertion_error_ll_exc_type, "AssertionError")
  78. ll_assert(etype != n_i_error_ll_exc_type, "NotImplementedError")
  79. exc_data.exc_type = etype
  80. exc_data.exc_value = evalue
  81. lloperation.llop.debug_start_traceback(lltype.Void, etype)
  82. def rpyexc_reraise(etype, evalue):
  83. exc_data.exc_type = etype
  84. exc_data.exc_value = evalue
  85. lloperation.llop.debug_reraise_traceback(lltype.Void, etype)
  86. def rpyexc_fetch_exception():
  87. evalue = rpyexc_fetch_value()
  88. rpyexc_clear()
  89. return evalue
  90. def rpyexc_restore_exception(evalue):
  91. if evalue:
  92. exc_data.exc_type = ll_inst_type(evalue)
  93. exc_data.exc_value = evalue
  94. self.rpyexc_occured_ptr = self.build_func(
  95. "RPyExceptionOccurred",
  96. rpyexc_occured,
  97. [], lltype.Bool)
  98. self.rpyexc_fetch_type_ptr = self.build_func(
  99. "RPyFetchExceptionType",
  100. rpyexc_fetch_type,
  101. [], self.lltype_of_exception_type)
  102. self.rpyexc_fetch_value_ptr = self.build_func(
  103. "RPyFetchExceptionValue",
  104. rpyexc_fetch_value,
  105. [], self.lltype_of_exception_value)
  106. self.rpyexc_clear_ptr = self.build_func(
  107. "RPyClearException",
  108. rpyexc_clear,
  109. [], lltype.Void)
  110. self.rpyexc_raise_ptr = self.build_func(
  111. "RPyRaiseException",
  112. self.noinline(rpyexc_raise),
  113. [self.lltype_of_exception_type, self.lltype_of_exception_value],
  114. lltype.Void,
  115. jitcallkind='rpyexc_raise') # for the JIT
  116. self.rpyexc_reraise_ptr = self.build_func(
  117. "RPyReRaiseException",
  118. rpyexc_reraise,
  119. [self.lltype_of_exception_type, self.lltype_of_exception_value],
  120. lltype.Void,
  121. jitcallkind='rpyexc_raise') # for the JIT
  122. self.rpyexc_fetch_exception_ptr = self.build_func(
  123. "RPyFetchException",
  124. rpyexc_fetch_exception,
  125. [], self.lltype_of_exception_value)
  126. self.rpyexc_restore_exception_ptr = self.build_func(
  127. "RPyRestoreException",
  128. self.noinline(rpyexc_restore_exception),
  129. [self.lltype_of_exception_value], lltype.Void)
  130. self.build_extra_funcs()
  131. self.mixlevelannotator.finish()
  132. self.lltype_to_classdef = translator.rtyper.lltype_to_classdef_mapping()
  133. def noinline(self, fn):
  134. fn = func_with_new_name(fn, fn.__name__)
  135. fn._dont_inline_ = True
  136. return fn
  137. def build_func(self, name, fn, inputtypes, rettype, **kwds):
  138. l2a = lltype_to_annotation
  139. graph = self.mixlevelannotator.getgraph(fn, map(l2a, inputtypes), l2a(rettype))
  140. return self.constant_func(name, inputtypes, rettype, graph,
  141. exception_policy="exc_helper", **kwds)
  142. def get_builtin_exception(self, Class):
  143. edata = self.translator.rtyper.exceptiondata
  144. bk = self.translator.annotator.bookkeeper
  145. error_def = bk.getuniqueclassdef(Class)
  146. error_ll_exc = edata.get_standard_ll_exc_instance(
  147. self.translator.rtyper, error_def)
  148. error_ll_exc_type = ll_inst_type(error_ll_exc)
  149. return error_ll_exc_type, error_ll_exc
  150. def transform_completely(self):
  151. for graph in self.translator.graphs:
  152. self.create_exception_handling(graph)
  153. def create_exception_handling(self, graph):
  154. """After an exception in a direct_call (or indirect_call), that is not caught
  155. by an explicit
  156. except statement, we need to reraise the exception. So after this
  157. direct_call we need to test if an exception had occurred. If so, we return
  158. from the current graph with a special value (False/-1/-1.0/null).
  159. Because of the added exitswitch we need an additional block.
  160. """
  161. if hasattr(graph, 'exceptiontransformed'):
  162. assert self.same_obj(self.exc_data_ptr, graph.exceptiontransformed)
  163. return
  164. else:
  165. self.raise_analyzer.analyze_direct_call(graph)
  166. graph.exceptiontransformed = self.exc_data_ptr
  167. join_blocks(graph)
  168. # collect the blocks before changing them
  169. n_need_exc_matching_blocks = 0
  170. n_gen_exc_checks = 0
  171. #
  172. entrymap = mkentrymap(graph)
  173. if graph.exceptblock in entrymap:
  174. for link in entrymap[graph.exceptblock]:
  175. self.transform_jump_to_except_block(graph, entrymap, link)
  176. #
  177. for block in list(graph.iterblocks()):
  178. self.replace_fetch_restore_operations(block)
  179. need_exc_matching, gen_exc_checks = self.transform_block(graph, block)
  180. n_need_exc_matching_blocks += need_exc_matching
  181. n_gen_exc_checks += gen_exc_checks
  182. cleanup_graph(graph)
  183. return n_need_exc_matching_blocks, n_gen_exc_checks
  184. def replace_fetch_restore_operations(self, block):
  185. # the gctransformer will create these operations. It looks as if the
  186. # order of transformations is important - but the gctransformer will
  187. # put them in a new graph, so all transformations will run again.
  188. for i in range(len(block.operations)):
  189. opname = block.operations[i].opname
  190. if opname == 'gc_fetch_exception':
  191. block.operations[i].opname = "direct_call"
  192. block.operations[i].args = [self.rpyexc_fetch_exception_ptr]
  193. elif opname == 'gc_restore_exception':
  194. block.operations[i].opname = "direct_call"
  195. block.operations[i].args.insert(0, self.rpyexc_restore_exception_ptr)
  196. elif opname == 'get_exception_addr': # only for lltype
  197. block.operations[i].opname = "direct_call"
  198. block.operations[i].args.insert(0, self.rpyexc_get_exception_addr_ptr)
  199. elif opname == 'get_exc_value_addr': # only for lltype
  200. block.operations[i].opname = "direct_call"
  201. block.operations[i].args.insert(0, self.rpyexc_get_exc_value_addr_ptr)
  202. def transform_block(self, graph, block):
  203. need_exc_matching = False
  204. n_gen_exc_checks = 0
  205. if block is graph.exceptblock:
  206. return need_exc_matching, n_gen_exc_checks
  207. elif block is graph.returnblock:
  208. return need_exc_matching, n_gen_exc_checks
  209. last_operation = len(block.operations) - 1
  210. if block.canraise:
  211. need_exc_matching = True
  212. last_operation -= 1
  213. elif (len(block.exits) == 1 and
  214. block.exits[0].target is graph.returnblock and
  215. len(block.operations) and
  216. (block.exits[0].args[0].concretetype is lltype.Void or
  217. block.exits[0].args[0] is block.operations[-1].result) and
  218. block.operations[-1].opname not in ('malloc', 'malloc_varsize')): # special cases
  219. last_operation -= 1
  220. lastblock = block
  221. for i in range(last_operation, -1, -1):
  222. op = block.operations[i]
  223. if not self.raise_analyzer.can_raise(op):
  224. continue
  225. splitlink = split_block(block, i+1)
  226. afterblock = splitlink.target
  227. if lastblock is block:
  228. lastblock = afterblock
  229. self.gen_exc_check(block, graph.returnblock, afterblock)
  230. n_gen_exc_checks += 1
  231. if need_exc_matching:
  232. assert lastblock.canraise
  233. if not self.raise_analyzer.can_raise(lastblock.operations[-1]):
  234. lastblock.exitswitch = None
  235. lastblock.recloseblock(lastblock.exits[0])
  236. lastblock.exits[0].exitcase = None
  237. else:
  238. self.insert_matching(lastblock, graph)
  239. return need_exc_matching, n_gen_exc_checks
  240. def comes_from_last_exception(self, entrymap, link):
  241. seen = set()
  242. pending = [(link, link.args[1])]
  243. while pending:
  244. link, v = pending.pop()
  245. if (link, v) in seen:
  246. continue
  247. seen.add((link, v))
  248. if link.last_exc_value is not None and v is link.last_exc_value:
  249. return True
  250. block = link.prevblock
  251. if block is None:
  252. continue
  253. for op in block.operations[::-1]:
  254. if v is op.result:
  255. if op.opname == 'cast_pointer':
  256. v = op.args[0]
  257. else:
  258. break
  259. for link in entrymap.get(block, ()):
  260. for v1, v2 in zip(link.args, block.inputargs):
  261. if v2 is v:
  262. pending.append((link, v1))
  263. return False
  264. def transform_jump_to_except_block(self, graph, entrymap, link):
  265. reraise = self.comes_from_last_exception(entrymap, link)
  266. result = Variable()
  267. result.concretetype = lltype.Void
  268. block = Block([v.copy() for v in graph.exceptblock.inputargs])
  269. if reraise:
  270. block.operations = [
  271. SpaceOperation("direct_call",
  272. [self.rpyexc_reraise_ptr] + block.inputargs,
  273. result),
  274. ]
  275. else:
  276. block.operations = [
  277. SpaceOperation("direct_call",
  278. [self.rpyexc_raise_ptr] + block.inputargs,
  279. result),
  280. SpaceOperation('debug_record_traceback', [],
  281. varoftype(lltype.Void)),
  282. ]
  283. link.target = block
  284. RETTYPE = graph.returnblock.inputargs[0].concretetype
  285. l = Link([error_constant(RETTYPE)], graph.returnblock)
  286. block.recloseblock(l)
  287. def insert_matching(self, block, graph):
  288. proxygraph, op = self.create_proxy_graph(block.operations[-1])
  289. block.operations[-1] = op
  290. #non-exception case
  291. block.exits[0].exitcase = block.exits[0].llexitcase = None
  292. # use the dangerous second True flag :-)
  293. inliner = inline.OneShotInliner(
  294. self.translator, graph, self.lltype_to_classdef,
  295. inline_guarded_calls=True, inline_guarded_calls_no_matter_what=True,
  296. raise_analyzer=self.raise_analyzer)
  297. inliner.inline_once(block, len(block.operations)-1)
  298. #block.exits[0].exitcase = block.exits[0].llexitcase = False
  299. def create_proxy_graph(self, op):
  300. """ creates a graph which calls the original function, checks for
  301. raised exceptions, fetches and then raises them again. If this graph is
  302. inlined, the correct exception matching blocks are produced."""
  303. # XXX slightly annoying: construct a graph by hand
  304. # but better than the alternative
  305. result = op.result.copy()
  306. opargs = []
  307. inputargs = []
  308. callargs = []
  309. ARGTYPES = []
  310. for var in op.args:
  311. if isinstance(var, Variable):
  312. v = Variable()
  313. v.concretetype = var.concretetype
  314. inputargs.append(v)
  315. opargs.append(v)
  316. callargs.append(var)
  317. ARGTYPES.append(var.concretetype)
  318. else:
  319. opargs.append(var)
  320. newop = SpaceOperation(op.opname, opargs, result)
  321. startblock = Block(inputargs)
  322. startblock.operations.append(newop)
  323. newgraph = FunctionGraph("dummy_exc1", startblock)
  324. startblock.closeblock(Link([result], newgraph.returnblock))
  325. newgraph.returnblock.inputargs[0].concretetype = op.result.concretetype
  326. self.gen_exc_check(startblock, newgraph.returnblock)
  327. excblock = Block([])
  328. llops = rtyper.LowLevelOpList(None)
  329. var_value = self.gen_getfield('exc_value', llops)
  330. var_type = self.gen_getfield('exc_type' , llops)
  331. #
  332. c_check1 = self.c_assertion_error_ll_exc_type
  333. c_check2 = self.c_n_i_error_ll_exc_type
  334. llops.genop('debug_catch_exception', [var_type, c_check1, c_check2])
  335. #
  336. self.gen_setfield('exc_value', self.c_null_evalue, llops)
  337. self.gen_setfield('exc_type', self.c_null_etype, llops)
  338. excblock.operations[:] = llops
  339. newgraph.exceptblock.inputargs[0].concretetype = self.lltype_of_exception_type
  340. newgraph.exceptblock.inputargs[1].concretetype = self.lltype_of_exception_value
  341. excblock.closeblock(Link([var_type, var_value], newgraph.exceptblock))
  342. startblock.exits[True].target = excblock
  343. startblock.exits[True].args = []
  344. fptr = self.constant_func("dummy_exc1", ARGTYPES, op.result.concretetype, newgraph)
  345. return newgraph, SpaceOperation("direct_call", [fptr] + callargs, op.result)
  346. def gen_exc_check(self, block, returnblock, normalafterblock=None):
  347. llops = rtyper.LowLevelOpList(None)
  348. spaceop = block.operations[-1]
  349. alloc_shortcut = self.check_for_alloc_shortcut(spaceop)
  350. if alloc_shortcut:
  351. var_no_exc = self.gen_nonnull(spaceop.result, llops)
  352. else:
  353. v_exc_type = self.gen_getfield('exc_type', llops)
  354. var_no_exc = self.gen_isnull(v_exc_type, llops)
  355. #
  356. # We could add a "var_no_exc is likely true" hint, but it seems
  357. # not to help, so it was commented out again.
  358. #var_no_exc = llops.genop('likely', [var_no_exc], lltype.Bool)
  359. block.operations.extend(llops)
  360. block.exitswitch = var_no_exc
  361. #exception occurred case
  362. b = Block([])
  363. b.operations = [SpaceOperation('debug_record_traceback', [],
  364. varoftype(lltype.Void))]
  365. l = Link([error_constant(returnblock.inputargs[0].concretetype)], returnblock)
  366. b.closeblock(l)
  367. l = Link([], b)
  368. l.exitcase = l.llexitcase = False
  369. #non-exception case
  370. l0 = block.exits[0]
  371. l0.exitcase = l0.llexitcase = True
  372. block.recloseblock(l0, l)
  373. insert_zeroing_op = False
  374. if spaceop.opname in ['malloc','malloc_varsize']:
  375. flavor = spaceop.args[1].value['flavor']
  376. if flavor == 'gc':
  377. insert_zeroing_op = True
  378. true_zero = spaceop.args[1].value.get('zero', False)
  379. # NB. when inserting more special-cases here, keep in mind that
  380. # you also need to list the opnames in transform_block()
  381. # (see "special cases")
  382. if insert_zeroing_op:
  383. if normalafterblock is None:
  384. normalafterblock = insert_empty_block(l0)
  385. v_result = spaceop.result
  386. if v_result in l0.args:
  387. result_i = l0.args.index(v_result)
  388. v_result_after = normalafterblock.inputargs[result_i]
  389. else:
  390. v_result_after = v_result.copy()
  391. l0.args.append(v_result)
  392. normalafterblock.inputargs.append(v_result_after)
  393. if true_zero:
  394. opname = "zero_everything_inside"
  395. else:
  396. opname = "zero_gc_pointers_inside"
  397. normalafterblock.operations.insert(
  398. 0, SpaceOperation(opname, [v_result_after],
  399. varoftype(lltype.Void)))
  400. def setup_excdata(self):
  401. EXCDATA = lltype.Struct('ExcData',
  402. ('exc_type', self.lltype_of_exception_type),
  403. ('exc_value', self.lltype_of_exception_value))
  404. self.EXCDATA = EXCDATA
  405. exc_data = lltype.malloc(EXCDATA, immortal=True)
  406. null_type = lltype.nullptr(self.lltype_of_exception_type.TO)
  407. null_value = lltype.nullptr(self.lltype_of_exception_value.TO)
  408. self.exc_data_ptr = exc_data
  409. self.cexcdata = Constant(exc_data, lltype.Ptr(self.EXCDATA))
  410. self.c_null_etype = Constant(null_type, self.lltype_of_exception_type)
  411. self.c_null_evalue = Constant(null_value, self.lltype_of_exception_value)
  412. return exc_data, null_type, null_value
  413. def constant_func(self, name, inputtypes, rettype, graph, **kwds):
  414. FUNC_TYPE = lltype.FuncType(inputtypes, rettype)
  415. fn_ptr = lltype.functionptr(FUNC_TYPE, name, graph=graph, **kwds)
  416. return Constant(fn_ptr, lltype.Ptr(FUNC_TYPE))
  417. def gen_getfield(self, name, llops):
  418. c_name = inputconst(lltype.Void, name)
  419. return llops.genop('getfield', [self.cexcdata, c_name],
  420. resulttype = getattr(self.EXCDATA, name))
  421. def gen_setfield(self, name, v_value, llops):
  422. c_name = inputconst(lltype.Void, name)
  423. llops.genop('setfield', [self.cexcdata, c_name, v_value])
  424. def gen_isnull(self, v, llops):
  425. return llops.genop('ptr_iszero', [v], lltype.Bool)
  426. def gen_nonnull(self, v, llops):
  427. return llops.genop('ptr_nonzero', [v], lltype.Bool)
  428. def same_obj(self, ptr1, ptr2):
  429. return ptr1._same_obj(ptr2)
  430. def check_for_alloc_shortcut(self, spaceop):
  431. if spaceop.opname in ('malloc', 'malloc_varsize'):
  432. return True
  433. elif spaceop.opname == 'direct_call':
  434. fnobj = spaceop.args[0].value._obj
  435. if hasattr(fnobj, '_callable'):
  436. oopspec = getattr(fnobj._callable, 'oopspec', None)
  437. if oopspec and oopspec == 'newlist(length)':
  438. return True
  439. return False
  440. def build_extra_funcs(self):
  441. EXCDATA = self.EXCDATA
  442. exc_data = self.exc_data_ptr
  443. def rpyexc_get_exception_addr():
  444. return (llmemory.cast_ptr_to_adr(exc_data) +
  445. llmemory.offsetof(EXCDATA, 'exc_type'))
  446. def rpyexc_get_exc_value_addr():
  447. return (llmemory.cast_ptr_to_adr(exc_data) +
  448. llmemory.offsetof(EXCDATA, 'exc_value'))
  449. self.rpyexc_get_exception_addr_ptr = self.build_func(
  450. "RPyGetExceptionAddr",
  451. rpyexc_get_exception_addr,
  452. [], llmemory.Address)
  453. self.rpyexc_get_exc_value_addr_ptr = self.build_func(
  454. "RPyGetExcValueAddr",
  455. rpyexc_get_exc_value_addr,
  456. [], llmemory.Address)