1. Florian Hahn
  2. pypy

Commits

Antonio Cuni  committed 3b9a5ed

(antocuni, arigato) backout the backout of the merge of refactor-call_release_gil:
hg backout 3ef424d4281f --parent bbcac902d4247dc6f71ef393908405d477015630

note that by doing this we are loosing the history in "hg blame", because all
the lines now belongs to this changeset. To see the real history, you can "hg
blame" at revision 71e2d672347d

  • Participants
  • Parent commits c25a11a
  • Branches refactor-call_release_gil

Comments (0)

Files changed (15)

File pypy/module/pypyjit/test_pypy_c/test__ffi.py

View file
         loops = log.loops_by_id('sleep')
         assert len(loops) == 1 # make sure that we actually JITted the loop
 
-
     def test_ctypes_call(self):
         from rpython.rlib.test.test_clibffi import get_libm_name
         def main(libm_name):
         # so far just check that call_release_gil() is produced.
         # later, also check that the arguments to call_release_gil()
         # are constants, and that the numerous raw_mallocs are removed
+
+    def test_cffi_call_guard_not_forced_fails(self):
+        # this is the test_pypy_c equivalent of
+        # rpython/jit/metainterp/test/test_fficall::test_guard_not_forced_fails
+        #
+        # it requires cffi to be installed for pypy in order to run
+        def main():
+            import sys
+            try:
+                import cffi
+            except ImportError:
+                sys.stderr.write('SKIP: cannot import cffi\n')
+                return 0
+                
+            ffi = cffi.FFI()
+
+            ffi.cdef("""
+            typedef void (*functype)(int);
+            int foo(int n, functype func);
+            """)
+
+            lib = ffi.verify("""
+            #include <signal.h>
+            typedef void (*functype)(int);
+
+            int foo(int n, functype func) {
+                if (n >= 2000) {
+                    func(n);
+                }
+                return n*2;
+            }
+            """)
+
+            @ffi.callback("functype")
+            def mycallback(n):
+                if n < 5000:
+                    return
+                # make sure that guard_not_forced fails
+                d = {}
+                f = sys._getframe()
+                while f:
+                    d.update(f.f_locals)
+                    f = f.f_back
+
+            n = 0
+            while n < 10000:
+                res = lib.foo(n, mycallback)  # ID: cfficall
+                # this is the real point of the test: before the
+                # refactor-call_release_gil branch, the assert failed when
+                # res == 5000
+                assert res == n*2
+                n += 1
+            return n
+
+        log = self.run(main, [], import_site=True)
+        assert log.result == 10000
+        loop, = log.loops_by_id('cfficall')
+        assert loop.match_by_id('cfficall', """
+            ...
+            f1 = call_release_gil(..., descr=<Calli 4 ii EF=6 OS=62>)
+            ...
+        """)

File rpython/jit/backend/llgraph/runner.py

View file
             # manipulation here (as a hack, instead of really doing
             # the aroundstate manipulation ourselves)
             return self.execute_call_may_force(descr, func, *args)
+        guard_op = self.lltrace.operations[self.current_index + 1]
+        assert guard_op.getopnum() == rop.GUARD_NOT_FORCED
+        self.force_guard_op = guard_op
         call_args = support.cast_call_args_in_order(descr.ARGS, args)
-        FUNC = lltype.FuncType(descr.ARGS, descr.RESULT)
-        func_to_call = rffi.cast(lltype.Ptr(FUNC), func)
-        result = func_to_call(*call_args)
+        #
+        func_adr = llmemory.cast_int_to_adr(func)
+        if hasattr(func_adr.ptr._obj, '_callable'):
+            # this is needed e.g. by test_fficall.test_guard_not_forced_fails,
+            # because to actually force the virtualref we need to llinterp the
+            # graph, not to directly execute the python function
+            result = self.cpu.maybe_on_top_of_llinterp(func, call_args, descr.RESULT)
+        else:
+            FUNC = lltype.FuncType(descr.ARGS, descr.RESULT)
+            func_to_call = rffi.cast(lltype.Ptr(FUNC), func)
+            result = func_to_call(*call_args)
+        del self.force_guard_op
         return support.cast_result(descr.RESULT, result)
 
     def execute_call_assembler(self, descr, *args):

File rpython/jit/backend/model.py

View file
 import weakref
 from rpython.rlib.debug import debug_start, debug_print, debug_stop
-from rpython.rtyper.lltypesystem import lltype
+from rpython.rtyper.lltypesystem import lltype, llmemory
 
 class CPUTotalTracker(object):
     total_compiled_loops = 0
     def typedescrof(self, TYPE):
         raise NotImplementedError
 
+    @staticmethod
+    def cast_int_to_ptr(x, TYPE):
+        x = llmemory.cast_int_to_adr(x)
+        return llmemory.cast_adr_to_ptr(x, TYPE)
+
     # ---------- the backend-dependent operations ----------
 
     # lltype specific operations

File rpython/jit/backend/x86/test/test_fficall.py

View file
 class TestFfiCall(Jit386Mixin, test_fficall.FfiCallTests):
     # for the individual tests see
     # ====> ../../../metainterp/test/test_fficall.py
-    pass
+
+    def _add_libffi_types_to_ll2types_maybe(self):
+        # this is needed by test_guard_not_forced_fails, because it produces a
+        # loop which reads the value of types.* in a variable, then a guard
+        # fail and we switch to blackhole: the problem is that at this point
+        # the blackhole interp has a real integer, but it needs to convert it
+        # back to a lltype pointer (which is handled by ll2ctypes, deeply in
+        # the logic). The workaround is to teach ll2ctypes in advance which
+        # are the addresses of the various types.* structures.
+        # Try to comment this code out and run the test to see how it fails :)
+        from rpython.rtyper.lltypesystem import rffi, lltype, ll2ctypes
+        from rpython.rlib.jit_libffi import types
+        for key, value in types.__dict__.iteritems():
+            if isinstance(value, lltype._ptr):
+                addr = rffi.cast(lltype.Signed, value)
+                ll2ctypes._int2obj[addr] = value

File rpython/jit/codewriter/jtransform.py

View file
             assert False, 'unsupported oopspec: %s' % oopspec_name
         return self._handle_oopspec_call(op, args, oopspecindex, extraeffect)
 
+    def rewrite_op_jit_ffi_save_result(self, op):
+        kind = op.args[0].value
+        assert kind in ('int', 'float')
+        return SpaceOperation('libffi_save_result_%s' % kind, op.args[1:], None)
+
     def rewrite_op_jit_force_virtual(self, op):
         return self._do_builtin_call(op)
 

File rpython/jit/metainterp/blackhole.py

View file
 from rpython.rlib.rarithmetic import intmask, LONG_BIT, r_uint, ovfcheck
 from rpython.rlib.rtimer import read_timestamp
 from rpython.rlib.unroll import unrolling_iterable
-from rpython.rtyper.lltypesystem import lltype, llmemory, rclass
+from rpython.rtyper.lltypesystem import lltype, llmemory, rclass, rffi
 from rpython.rtyper.lltypesystem.lloperation import llop
+from rpython.rlib.jit_libffi import CIF_DESCRIPTION_P
 
 
 def arguments(*argtypes, **kwds):
     def bhimpl_ll_read_timestamp():
         return read_timestamp()
 
+    @arguments("cpu", "i", "i", "i")
+    def bhimpl_libffi_save_result_int(self, cif_description, exchange_buffer, result):
+        ARRAY = lltype.Ptr(rffi.CArray(lltype.Signed))
+        cif_description = self.cast_int_to_ptr(cif_description, CIF_DESCRIPTION_P)
+        exchange_buffer = self.cast_int_to_ptr(exchange_buffer, rffi.CCHARP)
+        #
+        data_out = rffi.ptradd(exchange_buffer, cif_description.exchange_result)
+        rffi.cast(ARRAY, data_out)[0] = result
+
+    @arguments("cpu", "i", "i", "f")
+    def bhimpl_libffi_save_result_float(self, cif_description, exchange_buffer, result):
+        ARRAY = lltype.Ptr(rffi.CArray(lltype.Float))
+        cif_description = self.cast_int_to_ptr(cif_description, CIF_DESCRIPTION_P)
+        exchange_buffer = self.cast_int_to_ptr(exchange_buffer, rffi.CCHARP)
+        #
+        data_out = rffi.ptradd(exchange_buffer, cif_description.exchange_result)
+        rffi.cast(ARRAY, data_out)[0] = result
+
+
     # ----------
     # helpers to resume running in blackhole mode when a guard failed
 

File rpython/jit/metainterp/history.py

View file
 
     def show(self, errmsg=None):
         "NOT_RPYTHON"
-        from rpython.jit.metainterp.graphpage import display_loops
-        display_loops([self], errmsg)
+        from rpython.jit.metainterp.graphpage import display_procedures
+        display_procedures([self], errmsg)
 
     def check_consistency(self):     # for testing
         "NOT_RPYTHON"

File rpython/jit/metainterp/pyjitpl.py

View file
     def opimpl_ll_read_timestamp(self):
         return self.metainterp.execute_and_record(rop.READ_TIMESTAMP, None)
 
+    @arguments("box", "box", "box")
+    def opimpl_libffi_save_result_int(self, box_cif_description, box_exchange_buffer,
+                                      box_result):
+        from rpython.rtyper.lltypesystem import llmemory
+        from rpython.rlib.jit_libffi import CIF_DESCRIPTION_P
+        from rpython.jit.backend.llsupport.ffisupport import get_arg_descr
+
+        cif_description = box_cif_description.getint()
+        cif_description = llmemory.cast_int_to_adr(cif_description)
+        cif_description = llmemory.cast_adr_to_ptr(cif_description,
+                                                   CIF_DESCRIPTION_P)
+
+        kind, descr, itemsize = get_arg_descr(self.metainterp.cpu, cif_description.rtype)
+        
+        if kind != 'v':
+            ofs = cif_description.exchange_result
+            assert ofs % itemsize == 0     # alignment check (result)
+            self.metainterp.history.record(rop.SETARRAYITEM_RAW,
+                                           [box_exchange_buffer,
+                                            ConstInt(ofs // itemsize), box_result],
+                                           None, descr)
+
+    opimpl_libffi_save_result_float = opimpl_libffi_save_result_int
+
     # ------------------------------
 
     def setup_call(self, argboxes):
                                 box_arg, descr)
             arg_boxes.append(box_arg)
         #
-        kind, descr, itemsize = get_arg_descr(self.cpu, cif_description.rtype)
-        if kind == 'i':
-            box_result = history.BoxInt()
-        elif kind == 'f':
-            box_result = history.BoxFloat()
-        else:
-            assert kind == 'v'
-            box_result = None
+        box_result = op.result
         self.history.record(rop.CALL_RELEASE_GIL,
                             [op.getarg(2)] + arg_boxes,
                             box_result, calldescr)
         #
         self.history.operations.extend(extra_guards)
         #
-        if box_result is not None:
-            ofs = cif_description.exchange_result
-            assert ofs % itemsize == 0     # alignment check (result)
-            self.history.record(rop.SETARRAYITEM_RAW,
-                                [box_exchange_buffer,
-                                 ConstInt(ofs // itemsize), box_result],
-                                None, descr)
+        # note that the result is written back to the exchange_buffer by the
+        # special op libffi_save_result_{int,float}
 
     def direct_call_release_gil(self):
         op = self.history.operations.pop()

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

View file
-
 import py
+from _pytest.monkeypatch import monkeypatch
 import ctypes, math
 from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rtyper.annlowlevel import llhelper
 from rpython.jit.metainterp.test.support import LLJitMixin
 from rpython.rlib import jit
-from rpython.rlib.jit_libffi import types, CIF_DESCRIPTION, FFI_TYPE_PP
+from rpython.rlib import jit_libffi
+from rpython.rlib.jit_libffi import (types, CIF_DESCRIPTION, FFI_TYPE_PP,
+                                     jit_ffi_call, jit_ffi_save_result)
 from rpython.rlib.unroll import unrolling_iterable
 from rpython.rlib.rarithmetic import intmask
 
-
 def get_description(atypes, rtype):
     p = lltype.malloc(CIF_DESCRIPTION, len(atypes),
                       flavor='raw', immortal=True)
         p.atypes[i] = atypes[i]
     return p
 
+class FakeFFI(object):
+    """
+    Context manager to monkey patch jit_libffi with our custom "libffi-like"
+    function
+    """
+    
+    def __init__(self, fake_call_impl_any):
+        self.fake_call_impl_any = fake_call_impl_any
+        self.monkey = monkeypatch()
+        
+    def __enter__(self, *args):
+        self.monkey.setattr(jit_libffi, 'jit_ffi_call_impl_any', self.fake_call_impl_any)
+
+    def __exit__(self, *args):
+        self.monkey.undo()
+
 
 class FfiCallTests(object):
 
 
         unroll_avalues = unrolling_iterable(avalues)
 
-        @jit.oopspec("libffi_call(cif_description,func_addr,exchange_buffer)")
-        def fake_call(cif_description, func_addr, exchange_buffer):
+        def fake_call_impl_any(cif_description, func_addr, exchange_buffer):
             ofs = 16
             for avalue in unroll_avalues:
                 TYPE = rffi.CArray(lltype.typeOf(avalue))
                 rffi.cast(lltype.Ptr(TYPE), data)[0] = avalue
                 ofs += 16
 
-            fake_call(cif_description, func_addr, exbuf)
+            jit_ffi_call(cif_description, func_addr, exbuf)
 
             if rvalue is None:
                 res = 654321
             lltype.free(exbuf, flavor='raw')
             return res
 
-        res = f()
-        assert res == rvalue or (res, rvalue) == (654321, None)
-        res = self.interp_operations(f, [])
-        assert res == rvalue or (res, rvalue) == (654321, None)
-        self.check_operations_history(call_may_force=0,
-                                      call_release_gil=1)
+        with FakeFFI(fake_call_impl_any):
+            res = f()
+            assert res == rvalue or (res, rvalue) == (654321, None)
+            res = self.interp_operations(f, [])
+            assert res == rvalue or (res, rvalue) == (654321, None)
+            self.check_operations_history(call_may_force=0,
+                                          call_release_gil=1)
 
-    def test_simple_call(self):
+    def test_simple_call_int(self):
         self._run([types.signed] * 2, types.signed, [456, 789], -42)
 
     def test_many_arguments(self):
         self._run([types.signed], types.sint8, [456],
                   rffi.cast(rffi.SIGNEDCHAR, -42))
 
+    def _add_libffi_types_to_ll2types_maybe(self):
+        # not necessary on the llgraph backend, but needed for x86.
+        # see rpython/jit/backend/x86/test/test_fficall.py
+        pass
+
+    def test_guard_not_forced_fails(self):
+        self._add_libffi_types_to_ll2types_maybe()
+        FUNC = lltype.FuncType([lltype.Signed], lltype.Signed)
+
+        cif_description = get_description([types.slong], types.slong)
+        cif_description.exchange_args[0] = 16
+        cif_description.exchange_result = 32
+
+        ARRAY = lltype.Ptr(rffi.CArray(lltype.Signed))
+
+        @jit.dont_look_inside
+        def fn(n):
+            if n >= 50:
+                exctx.m = exctx.topframeref().n # forces the frame
+            return n*2
+
+        # this function simulates what a real libffi_call does: reading from
+        # the buffer, calling a function (which can potentially call callbacks
+        # and force frames) and write back to the buffer
+        def fake_call_impl_any(cif_description, func_addr, exchange_buffer):
+            # read the args from the buffer
+            data_in = rffi.ptradd(exchange_buffer, 16)
+            n = rffi.cast(ARRAY, data_in)[0]
+            #
+            # logic of the function
+            func_ptr = rffi.cast(lltype.Ptr(FUNC), func_addr)
+            n = func_ptr(n)
+            #
+            # write the result to the buffer
+            data_out = rffi.ptradd(exchange_buffer, 32)
+            rffi.cast(ARRAY, data_out)[0] = n
+
+        def do_call(n):
+            func_ptr = llhelper(lltype.Ptr(FUNC), fn)
+            exbuf = lltype.malloc(rffi.CCHARP.TO, 48, flavor='raw', zero=True)
+            data_in = rffi.ptradd(exbuf, 16)
+            rffi.cast(ARRAY, data_in)[0] = n
+            jit_ffi_call(cif_description, func_ptr, exbuf)
+            data_out = rffi.ptradd(exbuf, 32)
+            res = rffi.cast(ARRAY, data_out)[0]
+            lltype.free(exbuf, flavor='raw')
+            return res
+
+        #
+        #
+        class XY:
+            pass
+        class ExCtx:
+            pass
+        exctx = ExCtx()
+        myjitdriver = jit.JitDriver(greens = [], reds = ['n'])
+        def f():
+            n = 0
+            while n < 100:
+                myjitdriver.jit_merge_point(n=n)
+                xy = XY()
+                xy.n = n
+                exctx.topframeref = vref = jit.virtual_ref(xy)
+                res = do_call(n) # this is equivalent of a cffi call which
+                                 # sometimes forces a frame
+
+                # when n==50, fn() will force the frame, so guard_not_forced
+                # fails and we enter blackholing: this test makes sure that
+                # the result of call_release_gil is kept alive before the
+                # libffi_save_result, and that the corresponding box is passed
+                # in the fail_args. Before the fix, the result of
+                # call_release_gil was simply lost and when guard_not_forced
+                # failed, and the value of "res" was unpredictable.
+                # See commit b84ff38f34bd and subsequents.
+                assert res == n*2
+                jit.virtual_ref_finish(vref, xy)
+                exctx.topframeref = jit.vref_None
+                n += 1
+            return n
+
+        with FakeFFI(fake_call_impl_any):
+            assert f() == 100
+            res = self.meta_interp(f, [])
+            assert res == 100
+        
 
 class TestFfiCall(FfiCallTests, LLJitMixin):
     def test_jit_ffi_vref(self):

File rpython/rlib/jit_libffi.py

View file
 
 from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rtyper.extregistry import ExtRegistryEntry
 from rpython.rlib import clibffi, jit
+from rpython.rlib.nonconst import NonConstant
 
 
 FFI_CIF = clibffi.FFI_CIFP.TO
     return rffi.cast(lltype.Signed, res)
 
 
-@jit.oopspec("libffi_call(cif_description, func_addr, exchange_buffer)")
+# =============================
+# jit_ffi_call and its helpers
+# =============================
+
+## Problem: jit_ffi_call is turned into call_release_gil by pyjitpl. Before
+## the refactor-call_release_gil branch, the resulting code looked like this:
+##
+##     buffer = ...
+##     i0 = call_release_gil(...)
+##     guard_not_forced()
+##     setarray_item_raw(buffer, ..., i0)
+##
+## The problem is that the result box i0 was generated freshly inside pyjitpl,
+## and the codewriter did not know about its liveness: the result was that i0
+## was not in the fail_args of guard_not_forced. See
+## test_fficall::test_guard_not_forced_fails for a more detalied explanation
+## of the problem.
+##
+## The solution is to create a new separate operation libffi_save_result whose
+## job is to write the result in the exchange_buffer: during normal execution
+## this is a no-op because the buffer is already filled by libffi, but during
+## jitting the behavior is to actually write into the buffer.
+##
+## The result is that now the jitcode looks like this:
+##
+##     %i0 = libffi_call_int(...)
+##     -live-
+##     libffi_save_result_int(..., %i0)
+##
+## the "-live-" is the key, because it make sure that the value is not lost if
+## guard_not_forced fails.
+
+
 def jit_ffi_call(cif_description, func_addr, exchange_buffer):
     """Wrapper around ffi_call().  Must receive a CIF_DESCRIPTION_P that
     describes the layout of the 'exchange_buffer'.
     """
+    if cif_description.rtype == types.void:
+        jit_ffi_call_impl_void(cif_description, func_addr, exchange_buffer)
+    elif cif_description.rtype == types.double:
+        result = jit_ffi_call_impl_float(cif_description, func_addr, exchange_buffer)
+        jit_ffi_save_result('float', cif_description, exchange_buffer, result)
+    else:
+        result = jit_ffi_call_impl_int(cif_description, func_addr, exchange_buffer)
+        jit_ffi_save_result('int', cif_description, exchange_buffer, result)
+
+
+# we must return a NonConstant else we get the constant -1 as the result of
+# the flowgraph, and the codewriter does not produce a box for the
+# result. Note that when not-jitted, the result is unused, but when jitted the
+# box of the result contains the actual value returned by the C function.
+
+@jit.oopspec("libffi_call(cif_description,func_addr,exchange_buffer)")
+def jit_ffi_call_impl_int(cif_description, func_addr, exchange_buffer):
+    jit_ffi_call_impl_any(cif_description, func_addr, exchange_buffer)
+    return NonConstant(-1)
+
+@jit.oopspec("libffi_call(cif_description,func_addr,exchange_buffer)")
+def jit_ffi_call_impl_float(cif_description, func_addr, exchange_buffer):
+    jit_ffi_call_impl_any(cif_description, func_addr, exchange_buffer)
+    return NonConstant(-1.0)
+
+@jit.oopspec("libffi_call(cif_description,func_addr,exchange_buffer)")
+def jit_ffi_call_impl_void(cif_description, func_addr, exchange_buffer):
+    jit_ffi_call_impl_any(cif_description, func_addr, exchange_buffer)
+    return None
+
+def jit_ffi_call_impl_any(cif_description, func_addr, exchange_buffer):
+    """
+    This is the function which actually calls libffi. All the rest if just
+    infrastructure to convince the JIT to pass a typed result box to
+    jit_ffi_save_result
+    """
     buffer_array = rffi.cast(rffi.VOIDPP, exchange_buffer)
     for i in range(cif_description.nargs):
         data = rffi.ptradd(exchange_buffer, cif_description.exchange_args[i])
     clibffi.c_ffi_call(cif_description.cif, func_addr,
                        rffi.cast(rffi.VOIDP, resultdata),
                        buffer_array)
+    return -1
+
+
+
+def jit_ffi_save_result(kind, cif_description, exchange_buffer, result):
+    """
+    This is a no-op during normal execution, but actually fills the buffer
+    when jitted
+    """
+    pass
+
+class Entry(ExtRegistryEntry):
+    _about_ = jit_ffi_save_result
+
+    def compute_result_annotation(self, kind_s, *args_s):
+        from rpython.annotator import model as annmodel
+        assert isinstance(kind_s, annmodel.SomeString)
+        assert kind_s.const in ('int', 'float')
+
+    def specialize_call(self, hop):
+        hop.exception_cannot_occur()
+        vlist = hop.inputargs(lltype.Void, *hop.args_r[1:])
+        return hop.genop('jit_ffi_save_result', vlist,
+                         resulttype=lltype.Void)
+    
 
 # ____________________________________________________________
 

File rpython/rtyper/lltypesystem/lloperation.py

View file
     'jit_is_virtual':       LLOp(canrun=True),
     'jit_force_quasi_immutable': LLOp(canrun=True),
     'jit_record_known_class'  : LLOp(canrun=True),
+    'jit_ffi_save_result':  LLOp(canrun=True),
     'get_exception_addr':   LLOp(),
     'get_exc_value_addr':   LLOp(),
     'do_malloc_fixedsize_clear':LLOp(canmallocgc=True),

File rpython/rtyper/lltypesystem/opimpl.py

View file
 def op_jit_record_known_class(x, y):
     pass
 
+def op_jit_ffi_save_result(*args):
+    pass
+
 def op_get_group_member(TYPE, grpptr, memberoffset):
     from rpython.rtyper.lltypesystem import llgroup
     assert isinstance(memberoffset, llgroup.GroupMemberOffset)

File rpython/translator/c/funcgen.py

View file
     def OP_JIT_FORCE_QUASI_IMMUTABLE(self, op):
         return '/* JIT_FORCE_QUASI_IMMUTABLE %s */' % op
 
+    def OP_JIT_FFI_SAVE_RESULT(self, op):
+        return '/* JIT_FFI_SAVE_RESULT %s */' % op
+
     def OP_GET_GROUP_MEMBER(self, op):
         typename = self.db.gettype(op.result.concretetype)
         return '%s = (%s)_OP_GET_GROUP_MEMBER(%s, %s);' % (

File rpython/translator/cli/opcodes.py

View file
     'jit_force_virtual':        DoNothing,
     'jit_force_quasi_immutable':Ignore,
     'jit_is_virtual':           [PushPrimitive(ootype.Bool, False)],
+    'jit_ffi_save_result':      Ignore,
     }
 
 # __________ numeric operations __________

File rpython/translator/jvm/opcodes.py

View file
     'jit_force_virtual':        DoNothing,
     'jit_force_quasi_immutable': Ignore,
     'jit_is_virtual':           PushPrimitive(ootype.Bool, False),
+    'jit_ffi_save_result':      Ignore,
 
     'debug_assert':              [], # TODO: implement?
     'debug_start_traceback':    Ignore,