[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