Skip to content

Commit 86aab65

Browse files
authored
Merge pull request #1725 from clasp-developers/bytecode-step
Stepping in bytecode
2 parents 3c89efd + 3e27c66 commit 86aab65

File tree

10 files changed

+180
-64
lines changed

10 files changed

+180
-64
lines changed

include/clasp/core/step.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
3+
#include <clasp/core/foundation.h>
4+
#include <clasp/core/object.h>
5+
#include <clasp/core/lisp.h>
6+
7+
namespace core {
8+
9+
void breakstep(T_sp source, void* frame);
10+
void breakstep_args(void* frame, Function_sp fun, List_sp args);
11+
12+
}; // namespace core

src/core/bytecode.cc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <clasp/core/designators.h> // calledFunctionDesignator
1717
#include <clasp/core/evaluator.h> // eval::funcall
1818
#include <clasp/gctools/interrupt.h> // handle_all_queued_interrupts
19+
#include <clasp/core/step.h> // breakstep_arguments
1920

2021
#define VM_CODES
2122
#include <virtualMachine.h>
@@ -60,6 +61,19 @@ void BytecodeModule_O::register_for_debug() {
6061
newc->setCdr(old);
6162
}
6263

64+
// Note that we check stepping in the callER not the callEE.
65+
// This is so that we could provide the actual source forms, as we already do
66+
// in native code. TODO
67+
static void maybe_step_call(void* frame,
68+
Function_sp func, size_t nargs, T_O** rargs) {
69+
if (my_thread->_Breakstep) [[unlikely]] {
70+
ql::list args;
71+
for (size_t iarg = 0; iarg < nargs; ++iarg)
72+
args << T_sp((gctools::Tagged)rargs[iarg]);
73+
breakstep_args(frame, func, args.cons());
74+
}
75+
}
76+
6377
static inline int16_t read_s16(unsigned char* pc) {
6478
uint8_t byte0 = *pc;
6579
uint8_t byte1 = *(pc + 1);
@@ -259,6 +273,7 @@ bytecode_vm(VirtualMachine& vm, T_O** literals, T_O** closed, Closure_O* closure
259273
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
260274
Function_sp func = gc::As_assert<Function_sp>(tfunc);
261275
T_O** args = vm.stackref(sp, nargs - 1);
276+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
262277
// We push the PC for the debugger (see make_bytecode_frame in backtrace.cc)
263278
// We do this here rather than bytecode_call because e.g. we may call a
264279
// non-bytecode function, that in turn calls a bunch of different bytecode
@@ -281,6 +296,7 @@ bytecode_vm(VirtualMachine& vm, T_O** literals, T_O** closed, Closure_O* closure
281296
VM_RECORD_PLAYBACK(func, "vm_call_receive_one_func");
282297
VM_RECORD_PLAYBACK((void*)(uintptr_t)nargs, "vm_call_receive_one_nargs");
283298
T_O** args = vm.stackref(sp, nargs - 1);
299+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
284300
#if DEBUG_VM_RECORD_PLAYBACK == 1
285301
for (size_t ii = 0; ii < nargs; ii++) {
286302
stringstream name_args;
@@ -305,6 +321,7 @@ bytecode_vm(VirtualMachine& vm, T_O** literals, T_O** closed, Closure_O* closure
305321
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
306322
Function_sp func = gc::As_assert<Function_sp>(tfunc);
307323
T_O** args = vm.stackref(sp, nargs - 1);
324+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
308325
vm.push(sp, (T_O*)pc);
309326
vm._pc = pc;
310327
vm._stackPointer = sp;
@@ -658,6 +675,7 @@ bytecode_vm(VirtualMachine& vm, T_O** literals, T_O** closed, Closure_O* closure
658675
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
659676
Function_sp func = gc::As_assert<Function_sp>(tfunc);
660677
T_O** args = vm.stackref(sp, nargs - 1);
678+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
661679
vm.push(sp, (T_O*)pc);
662680
vm._pc = pc;
663681
vm._stackPointer = sp;
@@ -675,6 +693,7 @@ bytecode_vm(VirtualMachine& vm, T_O** literals, T_O** closed, Closure_O* closure
675693
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
676694
Function_sp func = gc::As_assert<Function_sp>(tfunc);
677695
T_O** args = vm.stackref(sp, nargs - 1);
696+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
678697
vm.push(sp, (T_O*)pc);
679698
vm._pc = pc;
680699
vm._stackPointer = sp;
@@ -693,6 +712,7 @@ bytecode_vm(VirtualMachine& vm, T_O** literals, T_O** closed, Closure_O* closure
693712
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
694713
Function_sp func = gc::As_assert<Function_sp>(tfunc);
695714
T_O** args = vm.stackref(sp, nargs - 1);
715+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
696716
vm.push(sp, (T_O*)pc);
697717
vm._pc = pc;
698718
vm._stackPointer = sp;
@@ -1045,6 +1065,7 @@ static unsigned char* long_dispatch(VirtualMachine& vm, unsigned char* pc, Multi
10451065
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
10461066
Function_sp func = gc::As_assert<Function_sp>(tfunc);
10471067
T_O** args = vm.stackref(sp, nargs - 1);
1068+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
10481069
vm.push(sp, (T_O*)pc);
10491070
vm._pc = pc;
10501071
vm._stackPointer = sp;
@@ -1060,9 +1081,10 @@ static unsigned char* long_dispatch(VirtualMachine& vm, unsigned char* pc, Multi
10601081
DBG_VM1("long call-receive-one %" PRIu16 "\n", nargs);
10611082
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
10621083
Function_sp func = gc::As_assert<Function_sp>(tfunc);
1084+
T_O** args = vm.stackref(sp, nargs - 1);
1085+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
10631086
VM_RECORD_PLAYBACK(func, "vm_call_receive_one_func");
10641087
VM_RECORD_PLAYBACK((void*)(uintptr_t)nargs, "vm_call_receive_one_nargs");
1065-
T_O** args = vm.stackref(sp, nargs - 1);
10661088
#if DEBUG_VM_RECORD_PLAYBACK == 1
10671089
for (size_t ii = 0; ii < nargs; ii++) {
10681090
stringstream name_args;
@@ -1089,6 +1111,7 @@ static unsigned char* long_dispatch(VirtualMachine& vm, unsigned char* pc, Multi
10891111
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
10901112
Function_sp func = gc::As_assert<Function_sp>(tfunc);
10911113
T_O** args = vm.stackref(sp, nargs - 1);
1114+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
10921115
vm.push(sp, (T_O*)pc);
10931116
vm._pc = pc;
10941117
vm._stackPointer = sp;
@@ -1307,6 +1330,7 @@ static unsigned char* long_dispatch(VirtualMachine& vm, unsigned char* pc, Multi
13071330
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
13081331
Function_sp func = gc::As_assert<Function_sp>(tfunc);
13091332
T_O** args = vm.stackref(sp, nargs - 1);
1333+
maybe_step_call(__builtin_frame_address(0), func, nargs, args);
13101334
vm.push(sp, (T_O*)pc);
13111335
vm._pc = pc;
13121336
vm._stackPointer = sp;

src/core/corePackage.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ SYMBOL_EXPORT_SC_(CorePkg, _PLUS_fe_overflow_PLUS_);
211211
SYMBOL_EXPORT_SC_(CorePkg, arguments);
212212
SYMBOL_EXPORT_SC_(CorePkg, array_out_of_bounds);
213213
SYMBOL_EXPORT_SC_(CorePkg, breakstep);
214+
SYMBOL_EXPORT_SC_(CorePkg, breakstep_arguments);
214215
SYMBOL_EXPORT_SC_(CorePkg, c_local);
215216
SYMBOL_EXPORT_SC_(CorePkg, circle_subst);
216217
SYMBOL_EXPORT_SC_(CorePkg, class_source_location)

src/core/cscript.lisp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#~"stackmap.cc"
1818
#~"debugger.cc"
1919
#~"debugger2.cc"
20+
#~"step.cc"
2021
#~"backtrace.cc"
2122
#~"bytecode.cc"
2223
#~"bytecode_compiler.cc"

src/core/debugger2.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <clasp/core/lispStream.h>
1414
#include <clasp/core/wrappers.h>
1515
#include <clasp/core/backtrace.h>
16+
#include <clasp/gctools/threadlocal.h> // ThreadLocalState access
1617

1718
namespace core {
1819

src/core/step.cc

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#include <clasp/core/foundation.h>
2+
#include <clasp/core/object.h>
3+
#include <clasp/core/lisp.h>
4+
#include <clasp/core/evaluator.h>
5+
#include <clasp/gctools/threadlocal.h>
6+
7+
namespace core {
8+
9+
// RAII thing to toggle breakstep while respecting nonlocal exit.
10+
// TODO: Check that this works with our unwinding? Not sure it does
11+
struct BreakstepToggle {
12+
ThreadLocalState* mthread;
13+
bool old_breakstep;
14+
BreakstepToggle(ThreadLocalState* thread, bool new_breakstep) {
15+
mthread = thread;
16+
old_breakstep = thread->_Breakstep;
17+
thread->_Breakstep = new_breakstep;
18+
}
19+
~BreakstepToggle() { mthread->_Breakstep = old_breakstep; }
20+
};
21+
22+
void breakstep(T_sp source, void* frame) {
23+
void* bframe = my_thread->_BreakstepFrame;
24+
// If bframe is NULL, we are doing step-into.
25+
// Otherwise, we are doing step-over, and we need to check
26+
// if we've returned yet. bframe is the frame step-over was initiated
27+
// from, and lframe/frame is the caller frame.
28+
// We have to check here because a function being stepped over may
29+
// nonlocally exit past the caller, and in that situation we want to
30+
// resume stepping.
31+
// FIXME: We assume stack growth direction here.
32+
if (!bframe || (frame >= bframe)) {
33+
// Make sure we don't invoke the stepper recursively,
34+
// but can do so again once we're out of the Lisp interaction.
35+
BreakstepToggle tog(my_thread, false);
36+
T_sp res = core::eval::funcall(core::_sym_breakstep, source);
37+
if (res.fixnump()) {
38+
switch (res.unsafe_fixnum()) {
39+
case 0:
40+
goto stop_stepping;
41+
case 1:
42+
my_thread->_BreakstepFrame = NULL;
43+
return;
44+
case 2:
45+
my_thread->_BreakstepFrame = frame;
46+
return;
47+
}
48+
}
49+
SIMPLE_ERROR("BUG: Unknown return value from {}: {}", _rep_(core::_sym_breakstep), _rep_(res));
50+
} else
51+
return;
52+
stop_stepping: // outside the scope of tog
53+
my_thread->_Breakstep = false;
54+
return;
55+
}
56+
57+
// when we have a call but no source - bytecode for now FIXME
58+
void breakstep_args(void* frame, Function_sp function, List_sp args) {
59+
void* bframe = my_thread->_BreakstepFrame;
60+
// FIXME: We assume stack growth direction here.
61+
if (!bframe || (frame >= bframe)) {
62+
// Make sure we don't invoke the stepper recursively,
63+
// but can do so again once we're out of the Lisp interaction.
64+
BreakstepToggle tog(my_thread, false);
65+
T_sp res = core::eval::funcall(core::_sym_breakstep_arguments,
66+
function, args);
67+
if (res.fixnump()) {
68+
switch (res.unsafe_fixnum()) {
69+
case 0:
70+
goto stop_stepping;
71+
case 1:
72+
my_thread->_BreakstepFrame = NULL;
73+
return;
74+
case 2:
75+
my_thread->_BreakstepFrame = frame;
76+
return;
77+
}
78+
}
79+
SIMPLE_ERROR("BUG: Unknown return value from {}: {}", _rep_(core::_sym_breakstep), _rep_(res));
80+
} else
81+
return;
82+
stop_stepping: // outside the scope of tog
83+
my_thread->_Breakstep = false;
84+
return;
85+
}
86+
87+
}; // namespace core

src/lisp/kernel/clos/conditions.lisp

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -493,14 +493,13 @@ format string."
493493
:FORMAT-ARGUMENTS format-arguments)))))
494494
nil)
495495

496-
(defun breakstep (source)
496+
(defun %breakstep (condition)
497497
"Pause due to stepping or a breakpoint."
498498
(clasp-debug:with-truncated-stack ()
499499
(restart-case
500500
(let ((*debugger-hook* nil))
501-
(invoke-debugger
502-
(make-condition 'clasp-debug:step-form :source source)))
503-
;; cc_breakstep interprets our return value as follows:
501+
(invoke-debugger condition))
502+
;; the C++ breakstep interprets our return value as follows:
504503
;; 0: continue without stepping
505504
;; 1: step-into
506505
;; 2: step-over
@@ -509,12 +508,21 @@ format string."
509508
:report "Resume normal, unstepped execution."
510509
0)
511510
(clasp-debug:step-into ()
512-
:report "Step into call."
511+
:report "Step into form."
513512
1)
514513
(clasp-debug:step-over ()
515-
:report "Step over call."
514+
:report "Step over form."
516515
2))))
517516

517+
;;; called from C++ - see step.cc
518+
(defun breakstep (source)
519+
(%breakstep (make-condition 'clasp-debug:step-form :source source)))
520+
(defun breakstep-arguments (function arguments &optional source)
521+
(%breakstep (make-condition 'clasp-debug:step-call
522+
:source source
523+
:function function :arguments arguments
524+
:arguments-available-p t)))
525+
518526
(defun warn (datum &rest arguments)
519527
"Args: (format-string &rest args)
520528
Formats FORMAT-STRING and ARGs to *ERROR-OUTPUT* as a warning message. Enters
@@ -1255,12 +1263,32 @@ The conflict resolver must be one of ~s" chosen-symbol candidates))
12551263
for value in values
12561264
collect (assert-prompt place-name value)))))))
12571265

1258-
(define-condition step-condition () ())
1266+
(define-condition clasp-debug:step-condition () ())
12591267

1260-
(define-condition clasp-debug:step-form (step-condition)
1268+
(define-condition clasp-debug:step-form (clasp-debug:step-condition)
12611269
((%source :initarg :source :reader source))
12621270
(:report (lambda (condition stream)
1263-
(format stream "Evaluating form: ~s" (source condition)))))
1271+
(format stream "Evaluating form:~%~t~s" (source condition)))))
1272+
1273+
(define-condition clasp-debug:step-call (clasp-debug:step-condition)
1274+
((%source :initarg :source :reader source)
1275+
(%called-function :initarg :function :reader called-function)
1276+
(%arguments :initarg :arguments :reader arguments)
1277+
(%arguments-available-p :initarg :arguments-available-p
1278+
:reader arguments-available-p))
1279+
(:report (lambda (condition stream)
1280+
(let ((form (source condition)))
1281+
(if form ; NIL is never really a call
1282+
(format stream "Evaluating form:~%~t~s" form)
1283+
(let* ((function (called-function condition))
1284+
(name (and function (core:function-name function)))
1285+
(dname (if (eq name 'cl:lambda)
1286+
"anonymous function"
1287+
name)))
1288+
(format stream "Calling ~a" name))))
1289+
(when (arguments-available-p condition)
1290+
(format stream "~%With arguments:~%~t~s"
1291+
(arguments condition))))))
12641292

12651293
;;; ----------------------------------------------------------------------
12661294
;;; Unicode, initially forgotten in clasp

src/lisp/kernel/lsp/debug.lisp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
;; misc
4444
(%export '(#:function-name-package))
4545
;; stepper
46-
(%export '(#:step-form #:step-into #:step-over))
46+
(%export '(#:step-condition #:step-form #:step-call
47+
#:step-into #:step-over))
4748
(import '(core:set-breakstep core:unset-breakstep core:breakstepping-p))
4849
(export '(core:set-breakstep core:unset-breakstep core:breakstepping-p))))
4950

src/lisp/regression-tests/debug.lisp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -264,23 +264,27 @@
264264
(let ((ext:*invoke-debugger-hook*
265265
(lambda (condition old-hook)
266266
(declare (ignore old-hook))
267-
(return (typep condition 'clasp-debug:step-form)))))
267+
(return (typep condition 'clasp-debug:step-condition)))))
268268
(step (print 4)))))
269269

270270
(test breakstepping-p
271-
(values (progn (clasp-debug:set-breakstep)
272-
(clasp-debug:breakstepping-p))
273-
(progn (clasp-debug:unset-breakstep)
274-
(clasp-debug:breakstepping-p)))
275-
(t nil))
271+
(let ((ext:*invoke-debugger-hook* ; don't step during the test!
272+
(lambda (condition old-hook)
273+
(declare (ignore condition old-hook))
274+
(invoke-restart 'clasp-debug:step-over))))
275+
(values (progn (clasp-debug:set-breakstep)
276+
(clasp-debug:breakstepping-p))
277+
(progn (clasp-debug:unset-breakstep)
278+
(clasp-debug:breakstepping-p))))
279+
(t nil))
276280

277281
;;; breakstep can also be used to enable the stepper, without STEP itself.
278282
(test-true breakstep
279283
(block nil
280284
(let ((ext:*invoke-debugger-hook*
281285
(lambda (condition old-hook)
282286
(declare (ignore old-hook))
283-
(return (typep condition 'clasp-debug:step-form)))))
287+
(return (typep condition 'clasp-debug:step-condition)))))
284288
(clasp-debug:set-breakstep)
285289
(unwind-protect
286290
(locally

0 commit comments

Comments
 (0)