[pypy-commit] pypy reverse-debugger: Callbacks.

arigo pypy.commits at gmail.com
Sat Jul 2 14:37:08 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: reverse-debugger
Changeset: r85517:00fce40d3026
Date: 2016-07-02 20:38 +0200
http://bitbucket.org/pypy/pypy/changeset/00fce40d3026/

Log:	Callbacks.

diff --git a/rpython/translator/c/funcgen.py b/rpython/translator/c/funcgen.py
--- a/rpython/translator/c/funcgen.py
+++ b/rpython/translator/c/funcgen.py
@@ -172,6 +172,13 @@
     # ____________________________________________________________
 
     def cfunction_body(self):
+        extra_return_text = None
+        if self.db.reverse_debugger:
+            from rpython.translator.revdb import gencsupp
+            (extra_enter_text, extra_return_text) = (
+                gencsupp.prepare_function(self))
+            if extra_enter_text:
+                yield extra_enter_text
         graph = self.graph
         yield 'goto block0;'    # to avoid a warning "this label is not used"
 
@@ -193,6 +200,8 @@
                 retval = self.expr(block.inputargs[0])
                 if self.exception_policy != "exc_helper":
                     yield 'RPY_DEBUG_RETURN();'
+                if extra_return_text:
+                    yield extra_return_text
                 yield 'return %s;' % retval
                 continue
             elif block.exitswitch is None:
@@ -405,13 +414,8 @@
                     line += '\nPYPY_INHIBIT_TAIL_CALL();'
                     break
         elif self.db.reverse_debugger:
-            if getattr(getattr(self.graph, 'func', None),
-                       '_revdb_do_all_calls_', False):
-                pass   # a hack for ll_call_destructor() to mean
-                       # that the calls should really be done
-            else:
-                from rpython.translator.revdb import gencsupp
-                line = gencsupp.emit(line, self.lltypename(v_result), r)
+            from rpython.translator.revdb import gencsupp
+            line = gencsupp.emit_residual_call(self, line, v_result, r)
         return line
 
     def OP_DIRECT_CALL(self, op):
diff --git a/rpython/translator/c/genc.py b/rpython/translator/c/genc.py
--- a/rpython/translator/c/genc.py
+++ b/rpython/translator/c/genc.py
@@ -735,6 +735,8 @@
                     print >> fc, '#include "preimpl.h"'
                     print >> fc, '#define PYPY_FILE_NAME "%s"' % name
                     print >> fc, '#include "src/g_include.h"'
+                    if self.database.reverse_debugger:
+                        print >> fc, '#include "revdb_def.h"'
                     print >> fc
                 print >> fc, MARKER
                 for node, impl in nodeiter:
@@ -904,6 +906,9 @@
         n = database.instrument_ncounter
         print >>fi, "#define PYPY_INSTRUMENT_NCOUNTER %d" % n
         fi.close()
+    if database.reverse_debugger:
+        from rpython.translator.revdb import gencsupp
+        gencsupp.write_revdb_def_file(database, targetdir.join('revdb_def.h'))
 
     eci = add_extra_files(database, eci)
     eci = eci.convert_sources_to_files()
diff --git a/rpython/translator/revdb/gencsupp.py b/rpython/translator/revdb/gencsupp.py
--- a/rpython/translator/revdb/gencsupp.py
+++ b/rpython/translator/revdb/gencsupp.py
@@ -11,6 +11,24 @@
         srcdir / 'revdb.c',
     ]
 
+def prepare_function(funcgen):
+    stack_bottom = False
+    for block in funcgen.graph.iterblocks():
+        for op in block.operations:
+            if op.opname == 'gc_stack_bottom':
+                stack_bottom = True
+    if stack_bottom:
+        name = funcgen.functionname
+        funcgen.db.stack_bottom_funcnames.append(name)
+        extra_enter_text = '\n'.join(
+            ['RPY_REVDB_CALLBACKLOC(RPY_CALLBACKLOC_%s);' % name] +
+            ['\t' + emit('/*arg*/', funcgen.lltypename(v), funcgen.expr(v))
+                for v in funcgen.graph.getargs()])
+        extra_return_text = '/* RPY_CALLBACK_LEAVE(); */'
+        return extra_enter_text, extra_return_text
+    else:
+        return None, None
+
 def emit_void(normal_code):
     return 'RPY_REVDB_EMIT_VOID(%s);' % (normal_code,)
 
@@ -19,6 +37,21 @@
         return emit_void(normal_code)
     return 'RPY_REVDB_EMIT(%s, %s, %s);' % (normal_code, cdecl(tp, '_e'), value)
 
+def emit_residual_call(funcgen, call_code, v_result, expr_result):
+    if getattr(getattr(funcgen.graph, 'func', None),
+               '_revdb_do_all_calls_', False):
+        return call_code   # a hack for ll_call_destructor() to mean
+                           # that the calls should really be done
+    # haaaaack
+    if call_code in ('RPyGilRelease();', 'RPyGilAcquire();'):
+        return '/* ' + call_code + ' */'
+    #
+    tp = funcgen.lltypename(v_result)
+    if tp == 'void @':
+        return 'RPY_REVDB_CALL_VOID(%s);' % (call_code,)
+    return 'RPY_REVDB_CALL(%s, %s, %s);' % (call_code, cdecl(tp, '_e'),
+                                            expr_result)
+
 def record_malloc_uid(expr):
     return ' RPY_REVDB_REC_UID(%s);' % (expr,)
 
@@ -67,3 +100,21 @@
 
     exports.EXPORTS_obj2name[s._as_obj()] = 'rpy_revdb_commands'
     db.get(s)
+
+    db.stack_bottom_funcnames = []
+
+def write_revdb_def_file(db, target_path):
+    funcnames = sorted(db.stack_bottom_funcnames)
+    f = target_path.open('w')
+    for i, fn in enumerate(funcnames):
+        print >> f, '#define RPY_CALLBACKLOC_%s %d' % (fn, i)
+    print >> f
+    print >> f, '#define RPY_CALLBACKLOCS \\'
+    funcnames = funcnames or ['NULL']
+    for i, fn in enumerate(funcnames):
+        if i == len(funcnames) - 1:
+            tail = ''
+        else:
+            tail = ', \\'
+        print >> f, '\t(void *)%s%s' % (fn, tail)
+    f.close()
diff --git a/rpython/translator/revdb/src-revdb/revdb.c b/rpython/translator/revdb/src-revdb/revdb.c
--- a/rpython/translator/revdb/src-revdb/revdb.c
+++ b/rpython/translator/revdb/src-revdb/revdb.c
@@ -16,6 +16,7 @@
 #include "structdef.h"
 #include "forwarddecl.h"
 #include "preimpl.h"
+#include "revdb_def.h"
 #include "src/rtyper.h"
 #include "src/mem.h"
 #include "src-revdb/revdb_include.h"
@@ -460,6 +461,20 @@
     return result;
 }
 
+RPY_EXTERN
+void rpy_reverse_db_callback_loc(int locnum)
+{
+    union {
+        unsigned char n[2];
+        uint16_t u;
+    } r;
+    assert(locnum < 0xFC00);
+    if (!RPY_RDB_REPLAY) {
+        RPY_REVDB_EMIT(r.n[0] = locnum >> 8; r.n[1] = locnum & 0xFF;,
+                       uint16_t _e, r.u);
+    }
+}
+
 
 /* ------------------------------------------------------------ */
 /* Replaying mode                                               */
@@ -862,8 +877,8 @@
     char dummy[1];
     if (rpy_revdb.buf_p != rpy_revdb.buf_limit - 1 ||
             read(rpy_rev_fileno, dummy, 1) > 0) {
-        fprintf(stderr, "RevDB file error: corrupted file (too much data or, "
-                        "more likely, non-deterministic run, e.g. a "
+        fprintf(stderr, "RevDB file error: too much data: corrupted file, "
+                        "revdb bug, or non-deterministic run, e.g. a "
                         "watchpoint with side effects)\n");
         exit(1);
     }
@@ -1320,6 +1335,35 @@
     fq_trigger();
 }
 
+static void *callbacklocs[] = {
+    RPY_CALLBACKLOCS     /* macro from revdb_def.h */
+};
+
+RPY_EXTERN
+void rpy_reverse_db_invoke_callback(unsigned char e)
+{
+    /* Replaying: we have read the byte which follows calls, expecting
+       to see 0xFC, but we saw something else.  It's part of a two-bytes
+       callback identifier. */
+
+    do {
+        unsigned long index;
+        unsigned char e2;
+        void (*pfn)(void);
+        _RPY_REVDB_EMIT_REPLAY(unsigned char _e, e2)
+        index = (e << 8) | e2;
+        if (index >= (sizeof(callbacklocs) / sizeof(callbacklocs[0]))) {
+            fprintf(stderr, "bad callback index\n");
+            exit(1);
+        }
+        pfn = callbacklocs[index];
+        pfn();
+
+        _RPY_REVDB_EMIT_REPLAY(unsigned char _e, e)
+    } while (e != 0xFC);
+}
+
+
 /* ------------------------------------------------------------ */
 
 
diff --git a/rpython/translator/revdb/src-revdb/revdb_include.h b/rpython/translator/revdb/src-revdb/revdb_include.h
--- a/rpython/translator/revdb/src-revdb/revdb_include.h
+++ b/rpython/translator/revdb/src-revdb/revdb_include.h
@@ -29,7 +29,7 @@
 RPY_EXTERN void rpy_reverse_db_setup(int *argc_p, char **argv_p[]);
 RPY_EXTERN void rpy_reverse_db_teardown(void);
 
-#if 1    /* enable to print locations to stderr of all the EMITs */
+#if 0    /* enable to print locations to stderr of all the EMITs */
 #  define _RPY_REVDB_PRINT(mode)                                        \
     fprintf(stderr,                                                     \
             "%s:%d: %0*llx\n",                                          \
@@ -37,7 +37,7 @@
             ((unsigned long long)_e) & ((2ULL << (8*sizeof(_e)-1)) - 1))
 #endif
 
-#if 1    /* enable to print all allocs to stderr */
+#if 0    /* enable to print all mallocs to stderr */
 RPY_EXTERN void seeing_uid(uint64_t uid);
 #  define _RPY_REVDB_PRUID()                                    \
     seeing_uid(uid);                                            \
@@ -54,17 +54,27 @@
 #endif
 
 
-#define RPY_REVDB_EMIT(normal_code, decl_e, variable)                   \
-    if (!RPY_RDB_REPLAY) {                                              \
-        normal_code                                                     \
+#define _RPY_REVDB_EMIT_RECORD(decl_e, variable)                        \
         {                                                               \
             decl_e = variable;                                          \
             _RPY_REVDB_PRINT("write");                                  \
             memcpy(rpy_revdb.buf_p, &_e, sizeof(_e));                   \
             if ((rpy_revdb.buf_p += sizeof(_e)) > rpy_revdb.buf_limit)  \
                 rpy_reverse_db_flush();                                 \
-        }                                                               \
-    } else {                                                            \
+        }
+
+#define _RPY_REVDB_EMIT_RECORD_EXTRA(extra, decl_e, variable)           \
+        {                                                               \
+            decl_e = variable;                                          \
+            _RPY_REVDB_PRINT("write");                                  \
+            rpy_revdb.buf_p[0] = extra;                                 \
+            memcpy(rpy_revdb.buf_p + 1, &_e, sizeof(_e));               \
+            if ((rpy_revdb.buf_p += 1 + sizeof(_e)) > rpy_revdb.buf_limit) \
+                rpy_reverse_db_flush();                                 \
+        }
+
+#define _RPY_REVDB_EMIT_REPLAY(decl_e, variable)                        \
+        {                                                               \
             decl_e;                                                     \
             char *_src = rpy_revdb.buf_p;                               \
             char *_end1 = _src + sizeof(_e);                            \
@@ -74,11 +84,45 @@
             if (_end1 >= rpy_revdb.buf_limit)                           \
                 rpy_reverse_db_fetch(__FILE__, __LINE__);               \
             variable = _e;                                              \
-    }
+        }
+
+#define RPY_REVDB_EMIT(normal_code, decl_e, variable)                   \
+    if (!RPY_RDB_REPLAY) {                                              \
+        normal_code                                                     \
+        _RPY_REVDB_EMIT_RECORD(decl_e, variable)                        \
+    } else                                                              \
+        _RPY_REVDB_EMIT_REPLAY(decl_e, variable)
 
 #define RPY_REVDB_EMIT_VOID(normal_code)                                \
     if (!RPY_RDB_REPLAY) { normal_code } else { }
 
+#define RPY_REVDB_CALL(call_code, decl_e, variable)                     \
+    if (!RPY_RDB_REPLAY) {                                              \
+        call_code                                                       \
+        _RPY_REVDB_EMIT_RECORD_EXTRA(0xFC, decl_e, variable)            \
+    } else {                                                            \
+        unsigned char _re;                                              \
+        _RPY_REVDB_EMIT_REPLAY(unsigned char _e, _re)                   \
+        if (_re != 0xFC)                                                \
+            rpy_reverse_db_invoke_callback(_re);                        \
+        _RPY_REVDB_EMIT_REPLAY(decl_e, variable)                        \
+    }
+
+#define RPY_REVDB_CALL_VOID(call_code)                                  \
+    if (!RPY_RDB_REPLAY) {                                              \
+        call_code                                                       \
+        _RPY_REVDB_EMIT_RECORD(unsigned char _e, 0xFC)                  \
+    }                                                                   \
+    else {                                                              \
+        unsigned char _re;                                              \
+        _RPY_REVDB_EMIT_REPLAY(unsigned char _e, _re)                   \
+        if (_re != 0xFC)                                                \
+            rpy_reverse_db_invoke_callback(_re);                        \
+    }
+
+#define RPY_REVDB_CALLBACKLOC(locnum)                                   \
+    rpy_reverse_db_callback_loc(locnum)
+
 #define RPY_REVDB_REC_UID(expr)                                         \
     do {                                                                \
         uint64_t uid = rpy_revdb.unique_id_seen;                        \
@@ -151,5 +195,7 @@
 RPY_EXTERN void *rpy_reverse_db_next_dead(void *result);
 RPY_EXTERN void rpy_reverse_db_register_destructor(void *obj, void(*)(void *));
 RPY_EXTERN void rpy_reverse_db_call_destructor(void *obj);
+RPY_EXTERN void rpy_reverse_db_invoke_callback(unsigned char);
+RPY_EXTERN void rpy_reverse_db_callback_loc(int);
 
 /* ------------------------------------------------------------ */
diff --git a/rpython/translator/revdb/test/test_basic.py b/rpython/translator/revdb/test/test_basic.py
--- a/rpython/translator/revdb/test/test_basic.py
+++ b/rpython/translator/revdb/test/test_basic.py
@@ -85,9 +85,14 @@
 
     def write_call(self, expected_string):
         x = self.next()   # raw_malloc: the pointer we got
+        self.same_thread()
         x = self.next(); assert x == len(expected_string)
+        self.same_thread()
         x = self.next('i'); assert x == 0      # errno
 
+    def same_thread(self):
+        x = self.next('c'); assert x == '\xFC'
+
 
 def compile(self, entry_point, backendopt=True,
             withsmallfuncsets=None):
diff --git a/rpython/translator/revdb/test/test_callback.py b/rpython/translator/revdb/test/test_callback.py
new file mode 100644
--- /dev/null
+++ b/rpython/translator/revdb/test/test_callback.py
@@ -0,0 +1,109 @@
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
+from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rlib.rarithmetic import intmask
+from rpython.rlib import revdb
+from rpython.translator.revdb.test.test_basic import BaseRecordingTests
+from rpython.translator.revdb.test.test_basic import InteractiveTests
+
+from rpython.translator.revdb.message import *
+
+
+def get_callback_demo():
+    eci = ExternalCompilationInfo(separate_module_sources=['''
+        int callme(int(*cb)(int)) {
+            return cb(40) * cb(3);
+        }
+    '''], post_include_bits=['''
+        int callme(int(*)(int));
+    '''])
+    FUNCPTR = lltype.Ptr(lltype.FuncType([rffi.INT], rffi.INT))
+    callme = rffi.llexternal('callme', [FUNCPTR], rffi.INT,
+                             compilation_info=eci)
+
+    def callback(n):
+        print intmask(n)
+        return n
+
+    def main(argv):
+        revdb.stop_point()
+        print intmask(callme(callback))
+        revdb.stop_point()
+        return 9
+
+    return main
+
+
+class TestRecording(BaseRecordingTests):
+
+    def test_callback_simple(self):
+        eci = ExternalCompilationInfo(separate_module_sources=['''
+            int callme(int(*cb)(int)) {
+                return cb(40) * cb(3);
+            }
+            int callmesimple(void) {
+                return 55555;
+            }
+        '''], post_include_bits=['''
+            int callme(int(*)(int));
+            int callmesimple(void);
+        '''])
+        FUNCPTR = lltype.Ptr(lltype.FuncType([rffi.INT], rffi.INT))
+        callme = rffi.llexternal('callme', [FUNCPTR], rffi.INT,
+                                 compilation_info=eci)
+        callmesimple = rffi.llexternal('callmesimple', [], rffi.INT,
+                                       compilation_info=eci)
+
+        def callback(n):
+            return intmask(n) * 100
+
+        def main(argv):
+            print intmask(callmesimple())
+            print intmask(callme(callback))
+            return 9
+        self.compile(main, backendopt=False)
+        out = self.run('Xx')
+        rdb = self.fetch_rdb([self.exename, 'Xx'])
+        rdb.same_thread()                       # callmesimple()
+        x = rdb.next('i'); assert x == 55555
+        rdb.write_call('55555\n')
+        b = rdb.next('!h'); assert 0 <= b < 10  # -> callback
+        x = rdb.next('i'); assert x == 40       # arg n
+        x = rdb.next('!h'); assert x == b       # -> callback
+        x = rdb.next('i'); assert x == 3        # arg n
+        rdb.same_thread()                       # <- return in main thread
+        x = rdb.next('i'); assert x == 4000 * 300   # return from callme()
+        rdb.write_call('%s\n' % (4000 * 300,))
+        x = rdb.next('q'); assert x == 0      # number of stop points
+        assert rdb.done()
+
+    def test_callback_with_effects(self):
+        main = get_callback_demo()
+        self.compile(main, backendopt=False)
+        out = self.run('Xx')
+        rdb = self.fetch_rdb([self.exename, 'Xx'])
+        b = rdb.next('!h'); assert 0 <= b < 10  # -> callback
+        x = rdb.next('i'); assert x == 40       # arg n
+        rdb.write_call('40\n')
+        x = rdb.next('!h'); assert x == b       # -> callback again
+        x = rdb.next('i'); assert x == 3        # arg n
+        rdb.write_call('3\n')
+        rdb.same_thread()                       # -> return in main thread
+        x = rdb.next('i'); assert x == 120      # <- return from callme()
+        rdb.write_call('120\n')
+        x = rdb.next('q'); assert x == 2        # number of stop points
+        assert rdb.done()
+
+
+class TestReplayingCallback(InteractiveTests):
+    expected_stop_points = 2
+
+    def setup_class(cls):
+        from rpython.translator.revdb.test.test_basic import compile, run
+        main = get_callback_demo()
+        compile(cls, main, backendopt=False)
+        run(cls, '')
+
+    def test_replaying_callback(self):
+        child = self.replay()
+        child.send(Message(CMD_FORWARD, 3))
+        child.expect(ANSWER_AT_END)
diff --git a/rpython/translator/revdb/test/test_weak.py b/rpython/translator/revdb/test/test_weak.py
--- a/rpython/translator/revdb/test/test_weak.py
+++ b/rpython/translator/revdb/test/test_weak.py
@@ -203,6 +203,7 @@
                 assert time == i + 1
                 y = intmask(rdb.next('q')); assert y == -1
                 triggered = True
+            rdb.same_thread()
             j = rdb.next()
             assert j == i + 1000000 * triggered
             if triggered:
@@ -214,6 +215,7 @@
                     assert uid > 0 and uid not in uid_seen
                     uid_seen.add(uid)
                     lst.append(uid)
+                rdb.same_thread()
                 totals.append((lst, intmask(rdb.next())))
         x = rdb.next('q'); assert x == 3000    # number of stop points
         #
@@ -243,11 +245,13 @@
                     assert x != -1
                     assert x not in seen_uids
                     seen_uids.add(x)
+                    rdb.same_thread()
                     y = intmask(rdb.next())
                     assert y == -7      # from the __del__
                     x = intmask(rdb.next())
                     if x == -1:
                         break
+            rdb.same_thread()
             x = rdb.next()
             assert x == len(seen_uids)
         assert len(seen_uids) == int(out)


More information about the pypy-commit mailing list