Author: Armin Rigo <ar...@tunes.org> Branch: reverse-debugger Changeset: r85485:259a2938c2c1 Date: 2016-07-01 11:59 +0200 http://bitbucket.org/pypy/pypy/changeset/259a2938c2c1/
Log: A fix that is also an optimization: when single-stepping on a loop while foo: body then it never jumps back to the first bytecode of the "while" line (it jumps back to the 2nd bytecode of that line). But we still want to see it when stepping in the loop. diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py --- a/pypy/interpreter/pyopcode.py +++ b/pypy/interpreter/pyopcode.py @@ -53,10 +53,14 @@ ### opcode dispatch ### def dispatch(self, pycode, next_instr, ec): + if self.space.config.translation.reverse_debugger: + from pypy.interpreter.reverse_debugging import prepare_code + prepare_code(pycode) + # # For the sequel, force 'next_instr' to be unsigned for performance next_instr = r_uint(next_instr) co_code = pycode.co_code - + # try: while True: next_instr = self.handle_bytecode(co_code, next_instr, ec) @@ -1055,6 +1059,11 @@ def jump_absolute(self, jumpto, ec): # this function is overridden by pypy.module.pypyjit.interp_jit check_nonneg(jumpto) + # + if self.space.config.translation.reverse_debugger: + from pypy.interpreter.reverse_debugging import jump_backward + jump_backward(self, jumpto) + # return jumpto def JUMP_FORWARD(self, jumpby, next_instr): @@ -1308,9 +1317,12 @@ self.space.setitem(w_dict, w_key, w_value) def LOAD_REVDB_VAR(self, oparg, next_instr): - from pypy.interpreter.reverse_debugging import load_metavar - w_var = load_metavar(oparg) - self.pushvalue(w_var) + if self.space.config.translation.reverse_debugger: + from pypy.interpreter.reverse_debugging import load_metavar + w_var = load_metavar(oparg) + self.pushvalue(w_var) + else: + self.MISSING_OPCODE(oparg, next_instr) ### ____________________________________________________________ ### diff --git a/pypy/interpreter/reverse_debugging.py b/pypy/interpreter/reverse_debugging.py --- a/pypy/interpreter/reverse_debugging.py +++ b/pypy/interpreter/reverse_debugging.py @@ -5,7 +5,7 @@ from rpython.rtyper.annlowlevel import cast_gcref_to_instance from pypy.interpreter.error import OperationError, oefmt from pypy.interpreter.baseobjspace import W_Root -from pypy.interpreter import gateway, typedef, pycode, pytraceback +from pypy.interpreter import gateway, typedef, pycode, pytraceback, pyframe from pypy.module.marshal import interp_marshal @@ -47,7 +47,11 @@ revdb.register_debug_command(revdb.CMD_WATCHVALUES, lambda_watchvalues) -pycode.PyCode.co_revdb_linestarts = None # or a string: an array of bits +pycode.PyCode.co_revdb_linestarts = None # or a string: see below + +# invariant: "f_revdb_nextline_instr" is the bytecode offset of +# the start of the line that follows "last_instr". +pyframe.PyFrame.f_revdb_nextline_instr = 0 def enter_call(caller_frame, callee_frame): @@ -64,6 +68,17 @@ if dbstate.breakpoint_stack_id == revdb.get_unique_id(caller_frame): revdb.breakpoint(-1) + +def jump_backward(frame, jumpto): + # When we see a jump backward, we set 'f_revdb_nextline_instr' in + # such a way that the next instruction, at 'jumpto', will trigger + # stop_point_at_start_of_line(). We have to trigger it even if + # 'jumpto' is not actually a start of line. For example, in a + # 'while foo: body', the body ends with a JUMP_ABSOLUTE which + # jumps back to the *second* opcode of the while. + frame.f_revdb_nextline_instr = jumpto + + def potential_stop_point(frame): if not we_are_translated(): return @@ -72,22 +87,49 @@ # Uses roughly the same algo as ExecutionContext.run_trace_func() # to know where the line starts are, but tweaked for speed, # avoiding the quadratic complexity when run N times with a large - # code object. A potential difference is that we only record - # where the line starts are; the "We jumped backwards in the same - # line" case of run_trace_func() is not fully reproduced. + # code object. # - code = frame.pycode - lstart = code.co_revdb_linestarts - if lstart is None: - lstart = build_co_revdb_linestarts(code) - index = frame.last_instr - c = lstart[index >> 3] - if ord(c) & (1 << (index & 7)): + cur = frame.last_instr + if cur < frame.f_revdb_nextline_instr: + return # fast path: we're still inside the same line as before + # + call_stop_point_at_line = True + co_revdb_linestarts = frame.pycode.co_revdb_linestarts + if cur > frame.f_revdb_nextline_instr: + # + # We jumped forward over the start of the next line. We're + # inside a different line, but we will only trigger a stop + # point if we're at the starting bytecode of that line. Fetch + # from co_revdb_linestarts the start of the line that is at or + # follows 'cur'. + ch = ord(co_revdb_linestarts[cur]) + if ch == 0: + pass # we are at the start of a line now + else: + # We are not, so don't call stop_point_at_start_of_line(). + # We still have to fill f_revdb_nextline_instr. + call_stop_point_at_line = False + # + if call_stop_point_at_line: stop_point_at_start_of_line() + cur += 1 + ch = ord(co_revdb_linestarts[cur]) + # + # Update f_revdb_nextline_instr. Check if 'ch' was greater than + # 255, in which case it was rounded down to 255 and we have to + # continue looking + nextline_instr = cur + ch + while ch == 255: + ch = ord(co_revdb_linestarts[nextline_instr]) + nextline_instr += ch + frame.f_revdb_nextline_instr = nextline_instr + def build_co_revdb_linestarts(code): - # inspired by findlinestarts() in the 'dis' standard module - bits = [False] * len(code.co_code) + # Inspired by findlinestarts() in the 'dis' standard module. + # Set up 'bits' so that it contains \x00 at line starts and \xff + # in-between. + bits = ['\xff'] * (len(code.co_code) + 1) if not code.hidden_applevel: lnotab = code.co_lnotab addr = 0 @@ -98,34 +140,35 @@ line_incr = ord(lnotab[p+1]) if byte_incr: if newline != 0: - if addr < len(bits): - bits[addr] = True + bits[addr] = '\x00' newline = 0 addr += byte_incr newline |= line_incr p += 2 if newline: - if addr < len(bits): - bits[addr] = True + bits[addr] = '\x00' + bits[len(code.co_code)] = '\x00' # - byte_list = [] - pending = 0 - nextval = 1 - for bit_is_set in bits: - if bit_is_set: - pending |= nextval - if nextval < 128: - nextval <<= 1 + # Change 'bits' so that the character at 'i', if not \x00, measures + # how far the next \x00 is + next_null = len(code.co_code) + p = next_null - 1 + while p >= 0: + if bits[p] == '\x00': + next_null = p else: - byte_list.append(chr(pending)) - pending = 0 - nextval = 1 - if nextval != 1: - byte_list.append(chr(pending)) - lstart = ''.join(byte_list) + ch = next_null - p + if ch > 255: ch = 255 + bits[p] = chr(ch) + p -= 1 + lstart = ''.join(bits) code.co_revdb_linestarts = lstart return lstart +def prepare_code(code): + if code.co_revdb_linestarts is None: + build_co_revdb_linestarts(code) + def get_final_lineno(code): lineno = code.co_firstlineno lnotab = code.co_lnotab diff --git a/pypy/interpreter/test/test_reverse_debugging.py b/pypy/interpreter/test/test_reverse_debugging.py --- a/pypy/interpreter/test/test_reverse_debugging.py +++ b/pypy/interpreter/test/test_reverse_debugging.py @@ -4,6 +4,7 @@ class FakeCode: + hidden_applevel = False def __init__(self, co_code, co_lnotab): self.co_firstlineno = 43 self.co_code = co_code @@ -26,7 +27,10 @@ for addr, lineno in dis.findlinestarts(code): expected_starts.add(addr) - for index in range(len(code.co_code)): - c = lstart[index >> 3] - found = ord(c) & (1 << (index & 7)) - assert (found != 0) == (index in expected_starts) + next_start = len(code.co_code) + for index in range(len(code.co_code), -1, -1): + if index in expected_starts: + next_start = index + assert lstart[index] == chr(next_start - index + if next_start - index <= 255 + else 255) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit