Skip to content

Commit 2756cbb

Browse files
authored
Merge pull request #1687 from clasp-developers/bytecode-frame-arguments
Bytecode frame arguments
2 parents 03ad40c + 35effca commit 2756cbb

File tree

5 files changed

+74
-40
lines changed

5 files changed

+74
-40
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
for weak-key-or-value tables, although at present they are actually
66
strong tables in practice.
77
* `ext:macroexpand-all` macroexpands a form and its subforms.
8+
* Arguments to bytecode functions are made available to debuggers.
89

910
## Fixed
1011
* Weak pointers and weak hash tables survive snapshot save/load.

include/clasp/core/bytecode.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,13 @@ class VMFrameDynEnv_O : public DynEnv_O {
348348

349349
namespace core {
350350

351+
// Offsets on the VM stack. These are pushed throughout bytecode.cc,
352+
// e.g. the PC is pushed by call instructions, and the FP by bytecode_call.
353+
const size_t BYTECODE_FRAME_PC_OFFSET = 3;
354+
const size_t BYTECODE_FRAME_NARGS_OFFSET = 2;
355+
const size_t BYTECODE_FRAME_ARGS_OFFSET = 1;
356+
const size_t BYTECODE_FRAME_FP_OFFSET = 0;
357+
351358
bool bytecode_module_contains_address_p(BytecodeModule_sp, void*);
352359
bool bytecode_function_contains_address_p(BytecodeSimpleFun_sp, void*);
353360
T_sp bytecode_function_for_pc(BytecodeModule_sp, void*);

src/core/backtrace.cc

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,18 @@ static DebuggerFrame_sp make_bytecode_frame_from_function(BytecodeSimpleFun_sp f
473473
T_sp closure = (fun->environmentSize() == 0) ? (T_sp)fun : nil<T_O>();
474474
List_sp bindings = bytecode_bindings_for_pc(fun->code(), bpc, bfp);
475475
T_sp spi = bytecode_spi_for_pc(fun->code(), bpc);
476-
return DebuggerFrame_O::make(fun->functionName(), Pointer_O::create(bpc), spi, fun->fdesc(), closure, nil<T_O>(), false, bindings,
476+
// Grab arguments.
477+
T_sp tnargs((gctools::Tagged)(*(bfp - BYTECODE_FRAME_NARGS_OFFSET)));
478+
size_t nargs = tnargs.unsafe_fixnum();
479+
T_O** argptr = (T_O**)*(bfp - BYTECODE_FRAME_ARGS_OFFSET);
480+
ql::list largs;
481+
for (size_t i = 0; i < nargs; ++i) {
482+
T_O* rarg = argptr[i];
483+
T_sp temp((gctools::Tagged)rarg);
484+
largs << temp;
485+
}
486+
// Finally make the frame.
487+
return DebuggerFrame_O::make(fun->functionName(), Pointer_O::create(bpc), spi, fun->fdesc(), closure, largs.cons(), true, bindings,
477488
INTERN_(kw, bytecode), false);
478489
}
479490

@@ -482,9 +493,8 @@ static DebuggerFrame_sp make_bytecode_frame(size_t frameIndex, unsigned char*& p
482493
void* bpc = pc;
483494
T_O** bfp = fp;
484495
if (fp) { // null fp means we've hit the end.
485-
// PC was pushed just before the frame pointer.
486-
pc = (unsigned char*)(*(fp - 1));
487-
fp = (T_O**)(*fp);
496+
pc = (unsigned char*)(*(fp - BYTECODE_FRAME_PC_OFFSET));
497+
fp = (T_O**)(*(fp - BYTECODE_FRAME_FP_OFFSET));
488498
}
489499
// Find the bytecode module containing the current pc.
490500
List_sp modules = _lisp->_Roots._AllBytecodeModules.load(std::memory_order_relaxed);

src/core/bytecode.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ bytecode_vm(VirtualMachine& vm, T_O** literals, T_O** closed, Closure_O* closure
259259
T_sp tfunc((gctools::Tagged)(*(vm.stackref(sp, nargs))));
260260
Function_sp func = gc::As_assert<Function_sp>(tfunc);
261261
T_O** args = vm.stackref(sp, nargs - 1);
262+
// We push the PC for the debugger (see make_bytecode_frame in backtrace.cc)
263+
// We do this here rather than bytecode_call because e.g. we may call a
264+
// non-bytecode function, that in turn calls a bunch of different bytecode
265+
// functions, which may trash vm._pc making it unsuitable.
266+
// We have to do this for all call instructions, not just this one.
262267
vm.push(sp, (T_O*)pc);
263268
vm._pc = pc;
264269
vm._stackPointer = sp;
@@ -1510,6 +1515,12 @@ gctools::return_type bytecode_call(unsigned char* pc, core::T_O* lcc_closure, si
15101515
// being unwound to.
15111516
core::T_O** old_fp = vm._framePointer;
15121517
core::T_O** old_sp = vm._stackPointer;
1518+
// Push the args and FP for debugging (see backtrace.cc)
1519+
// This is mildly wasteful of stack space, but when calling bytecode from
1520+
// non-bytecode the arguments won't be on the VM stack, so this is the
1521+
// best I got.
1522+
vm.push(vm._stackPointer, core::make_fixnum(lcc_nargs).raw_());
1523+
vm.push(vm._stackPointer, (core::T_O*)lcc_args);
15131524
vm.push(vm._stackPointer, (core::T_O*)old_fp);
15141525
core::T_O** fp = vm._framePointer = vm._stackPointer;
15151526
core::T_O** sp = vm.push_frame(fp, nlocals);

src/lisp/kernel/lsp/debug.lisp

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,43 @@ If the arguments are not available, returns NIL NIL."
143143
(bind-var (first auxs) (second auxs)))
144144
(nreverse bindings))))
145145

146-
(defun frame-locals (frame &key eval
147-
&aux (fname (frame-function-name frame)))
146+
(defun locals-from-arguments (frame eval &aux (fname (frame-function-name frame)))
147+
(multiple-value-bind (args args-available) (frame-arguments frame)
148+
(multiple-value-bind (lambda-list lambda-list-available)
149+
(frame-function-lambda-list frame)
150+
(cond
151+
((not args-available) nil)
152+
(lambda-list-available
153+
(lambda-list-alist lambda-list args eval))
154+
;; The frame is missing a lambda list so fallback to just naming the arguments sequentially.
155+
((and (consp fname)
156+
(eq (first fname) 'cl:method)
157+
(= (length args) 2)
158+
(core:vaslistp (first args)))
159+
;; This is non-fast method. The real arguments are a vaslist in
160+
;; the first element and the method list is in the second element.
161+
(let ((method-args (core:list-from-vaslist (first args)))
162+
(next-methods (second args)))
163+
(append
164+
(let ((result ()))
165+
(do ((args method-args (cdr method-args))
166+
(i 0 (1+ i)))
167+
((null args) (nreverse result))
168+
(push (cons (intern (format nil "ARG~d" i) :cl-user)
169+
(first args))
170+
result)))
171+
(list (cons 'cl-user::next-methods next-methods)))))
172+
(t
173+
;; This is a fast method. Just treat it normally.
174+
(let ((result ()))
175+
(do ((args args (cdr args))
176+
(i 0 (1+ i)))
177+
((null args) (nreverse result))
178+
(push (cons (intern (format nil "ARG~d" i) :cl-user)
179+
(first args))
180+
result))))))))
181+
182+
(defun frame-locals (frame &key eval)
148183
"Return an alist of local lexical/special variables and their values at the continuation the frame
149184
represents. The CARs are variable names and CDRs their values. Multiple bindings with the same
150185
name may be returned, as there is no notion of lexical scope in this interface. By default
@@ -154,40 +189,10 @@ If the arguments are not available, returns NIL NIL."
154189
(append
155190
;; This only gives anything for bytecode functions right now.
156191
(core:debugger-frame-locals frame)
157-
(multiple-value-bind (args args-available) (frame-arguments frame)
158-
(multiple-value-bind (lambda-list lambda-list-available)
159-
(frame-function-lambda-list frame)
160-
(cond
161-
((not args-available) nil)
162-
(lambda-list-available
163-
(lambda-list-alist lambda-list args eval))
164-
;; The frame is missing a lambda list so fallback to just naming the arguments sequentially.
165-
((and (consp fname)
166-
(eq (first fname) 'cl:method)
167-
(= (length args) 2)
168-
(core:vaslistp (first args)))
169-
;; This is non-fast method. The real arguments are a vaslist in
170-
;; the first element and the method list is in the second element.
171-
(let ((method-args (core:list-from-vaslist (first args)))
172-
(next-methods (second args)))
173-
(append
174-
(let ((result ()))
175-
(do ((args method-args (cdr method-args))
176-
(i 0 (1+ i)))
177-
((null args) (nreverse result))
178-
(push (cons (intern (format nil "ARG~d" i) :cl-user)
179-
(first args))
180-
result)))
181-
(list (cons 'cl-user::next-methods next-methods)))))
182-
(t
183-
;; This is a fast method. Just treat it normally.
184-
(let ((result ()))
185-
(do ((args args (cdr args))
186-
(i 0 (1+ i)))
187-
((null args) (nreverse result))
188-
(push (cons (intern (format nil "ARG~d" i) :cl-user)
189-
(first args))
190-
result)))))))))
192+
(if (eq (core:debugger-frame-lang frame) :bytecode)
193+
;; bytecode frames already have good locals, so don't bother w/arguments
194+
nil
195+
(locals-from-arguments frame eval))))
191196

192197
(defun frame-function-lambda-list (frame)
193198
"Return the lambda list of the function being called in this frame, and a second value indicating success. This function may fail, in which case the first value is undefined and the second is NIL. In success the first value is the lambda list and the second value is true."

0 commit comments

Comments
 (0)