The inspector says where a slot's value is stored
This commit is contained in:
parent
85b9f35a66
commit
377ada8d27
9
TODO.org
9
TODO.org
@ -1721,8 +1721,13 @@ form is evaluated again plainly.
|
|||||||
Both were daemon ops with nothing calling them. One command shows the backtrace
|
Both were daemon ops with nothing calling them. One command shows the backtrace
|
||||||
with the selected frame's locals.
|
with the selected frame's locals.
|
||||||
|
|
||||||
** TODO Hex, binary and an address on a primitive in the inspector
|
** DONE Hex, binary and an address on a primitive in the inspector
|
||||||
The last item of the Emacs batch besides the break buffer, and independent of it.
|
CLOSED: [2026-09-25]
|
||||||
|
Hex and binary were already drawn under every integer. The slot root's reply
|
||||||
|
now carries =:addr=, the address of the place it read (a field or element down
|
||||||
|
a path included), and the inspector shows it as =at 0x…=. An expression root has
|
||||||
|
no address, because its value is not stored anywhere. A stack address is not
|
||||||
|
offered to =flan-inspect-address=, since the registry does not follow one.
|
||||||
|
|
||||||
** DONE A defclass is not on the definitions list as a type, and a sum's cases are not drawn
|
** DONE A defclass is not on the definitions list as a type, and a sum's cases are not drawn
|
||||||
CLOSED: [2026-09-25]
|
CLOSED: [2026-09-25]
|
||||||
|
|||||||
@ -467,7 +467,9 @@ drew. It reaches an **option's payload** and a **union case's fields**, which
|
|||||||
have offsets but no accessor form to write. In exchange it needs a **stopped**
|
have offsets but no accessor form to write. In exchange it needs a **stopped**
|
||||||
program, and it is refused — by name, with the reason — once the program
|
program, and it is refused — by name, with the reason — once the program
|
||||||
resumes or if the frame's body was redefined since the frame was entered. It
|
resumes or if the frame's body was redefined since the frame was entered. It
|
||||||
cannot start from an expression at all.
|
cannot start from an expression at all. Because it reads a place in the stopped
|
||||||
|
frame, the buffer also shows where that place is stored, as `at 0x…` under the
|
||||||
|
value.
|
||||||
|
|
||||||
**An address** — `M-x flan-inspect-address`. A number, `#x7f…` or decimal, of
|
**An address** — `M-x flan-inspect-address`. A number, `#x7f…` or decimal, of
|
||||||
the kind a debugger, a valgrind report or a C shim's `printf` hands you. There
|
the kind a debugger, a valgrind report or a C shim's `printf` hands you. There
|
||||||
|
|||||||
@ -426,6 +426,13 @@ an atom does not carry its type.")
|
|||||||
Kept because it is the thing edit mode hands you: a Flan literal is what the
|
Kept because it is the thing edit mode hands you: a Flan literal is what the
|
||||||
renderer writes, so the buffer you type into is the value's own spelling and
|
renderer writes, so the buffer you type into is the value's own spelling and
|
||||||
not a second notation invented for editing it.")
|
not a second notation invented for editing it.")
|
||||||
|
(defvar-local flan-inspect--addr nil
|
||||||
|
"Where the value on screen is stored, as a number, or nil.
|
||||||
|
The slot root answers with it, since it is reading a place in a stopped frame,
|
||||||
|
and so does the address root. An expression root's reply carries none: the
|
||||||
|
value it renders is the result of evaluating the expression, which is not
|
||||||
|
stored anywhere the program keeps.")
|
||||||
|
|
||||||
(defvar-local flan-inspect--at-stop nil
|
(defvar-local flan-inspect--at-stop nil
|
||||||
"Which stop this buffer was drawn at, as the daemon numbered it.
|
"Which stop this buffer was drawn at, as the daemon numbered it.
|
||||||
Nil where the reply did not say — only the slot root does, because only it
|
Nil where the reply did not say — only the slot root does, because only it
|
||||||
@ -511,9 +518,10 @@ stated honestly — every Flan integer is rendered through i64."
|
|||||||
('option (format "(some …)"))
|
('option (format "(some …)"))
|
||||||
(_ (plist-get node :text))))
|
(_ (plist-get node :text))))
|
||||||
|
|
||||||
(defun flan-inspect--render (root path node stack &optional declared)
|
(defun flan-inspect--render (root path node stack &optional declared addr)
|
||||||
"Draw NODE, reached by ROOT walked by PATH, with STACK behind it.
|
"Draw NODE, reached by ROOT walked by PATH, with STACK behind it.
|
||||||
DECLARED is the type the daemon named, when it named one."
|
DECLARED is the type the daemon named, when it named one. ADDR is where the
|
||||||
|
value is stored, when the reply said."
|
||||||
(let ((inhibit-read-only t))
|
(let ((inhibit-read-only t))
|
||||||
(erase-buffer)
|
(erase-buffer)
|
||||||
(insert (propertize (flan-inspect--root-label root path)
|
(insert (propertize (flan-inspect--root-label root path)
|
||||||
@ -537,6 +545,10 @@ DECLARED is the type the daemon named, when it named one."
|
|||||||
;; someone opened the inspector on a number *for*, and it is long.
|
;; someone opened the inspector on a number *for*, and it is long.
|
||||||
(let ((detail (flan-inspect--detail node)))
|
(let ((detail (flan-inspect--detail node)))
|
||||||
(when detail (insert (propertize (concat detail "\n") 'face 'shadow))))
|
(when detail (insert (propertize (concat detail "\n") 'face 'shadow))))
|
||||||
|
;; Where it lives, in the base an address is read in. An address root
|
||||||
|
;; names its address in the line above already.
|
||||||
|
(when (and addr (not (eq (car-safe root) :addr)))
|
||||||
|
(insert (propertize (format "at 0x%X\n" addr) 'face 'shadow)))
|
||||||
;; The stack made visible. CIDER keeps it and does not show it; here it is
|
;; The stack made visible. CIDER keeps it and does not show it; here it is
|
||||||
;; the difference between a value and *which* value, and the thing that was
|
;; the difference between a value and *which* value, and the thing that was
|
||||||
;; typed at the root is often several steps back by now.
|
;; typed at the root is often several steps back by now.
|
||||||
@ -644,6 +656,7 @@ and whether that may be followed is the answer"))
|
|||||||
"flan: the program answered without a value for %s"
|
"flan: the program answered without a value for %s"
|
||||||
(flan-inspect--root-label root path)))
|
(flan-inspect--root-label root path)))
|
||||||
:type (plist-get r :type)
|
:type (plist-get r :type)
|
||||||
|
:addr (plist-get r :addr)
|
||||||
;; The stop the read happened at, where the reply carried one. It
|
;; The stop the read happened at, where the reply carried one. It
|
||||||
;; travels with the value rather than being asked for separately,
|
;; travels with the value rather than being asked for separately,
|
||||||
;; because asked separately it would be a second question about a
|
;; because asked separately it would be a second question about a
|
||||||
@ -663,6 +676,7 @@ and whether that may be followed is the answer"))
|
|||||||
(setq flan-inspect--node (flan-inspect-parse flan-inspect--rendered))
|
(setq flan-inspect--node (flan-inspect-parse flan-inspect--rendered))
|
||||||
(setq flan-inspect--type (plist-get answer :type))
|
(setq flan-inspect--type (plist-get answer :type))
|
||||||
(setq flan-inspect--at-stop (plist-get answer :at-stop))
|
(setq flan-inspect--at-stop (plist-get answer :at-stop))
|
||||||
|
(setq flan-inspect--addr (plist-get answer :addr))
|
||||||
(setq flan-inspect--stack stack)
|
(setq flan-inspect--stack stack)
|
||||||
(setq flan-inspect--editing nil)
|
(setq flan-inspect--editing nil)
|
||||||
;; Put back what editing turned off, here and not in the two commands
|
;; Put back what editing turned off, here and not in the two commands
|
||||||
@ -673,7 +687,7 @@ and whether that may be followed is the answer"))
|
|||||||
(setq buffer-read-only t)
|
(setq buffer-read-only t)
|
||||||
(setq-local truncate-lines t)
|
(setq-local truncate-lines t)
|
||||||
(flan-inspect--render root path flan-inspect--node stack
|
(flan-inspect--render root path flan-inspect--node stack
|
||||||
flan-inspect--type))
|
flan-inspect--type flan-inspect--addr))
|
||||||
(display-buffer buf)
|
(display-buffer buf)
|
||||||
buf))
|
buf))
|
||||||
|
|
||||||
|
|||||||
@ -196,7 +196,20 @@
|
|||||||
(let ((text (with-current-buffer (save-window-excursion (flan-inspect "flags"))
|
(let ((text (with-current-buffer (save-window-excursion (flan-inspect "flags"))
|
||||||
(buffer-string))))
|
(buffer-string))))
|
||||||
(test-flan--check "a number opened on its own shows its bases"
|
(test-flan--check "a number opened on its own shows its bases"
|
||||||
(string-match-p "0xFF 0b1111_1111" text))))
|
(string-match-p "0xFF 0b1111_1111" text))
|
||||||
|
(test-flan--check "an expression's value has no address line"
|
||||||
|
(not (string-match-p "^at 0x" text)))))
|
||||||
|
|
||||||
|
;; Where a slot's value is stored, which the slot root's reply carries: under
|
||||||
|
;; the value, in hex.
|
||||||
|
(let ((flan-inspect-request-function
|
||||||
|
(lambda (_) '(:status "ok" :type "i32" :value "7" :addr 140737488345360)))
|
||||||
|
(flan-inspect-buffer " *test-inspect*"))
|
||||||
|
(let ((text (with-current-buffer
|
||||||
|
(save-window-excursion (flan-inspect-slot 1 0 "n"))
|
||||||
|
(buffer-string))))
|
||||||
|
(test-flan--check "a slot's value says where it is stored"
|
||||||
|
(string-match-p "^at 0x7FFFFFFFD910$" text))))
|
||||||
|
|
||||||
(let ((flan-inspect-request-function
|
(let ((flan-inspect-request-function
|
||||||
(lambda (_) '(:status "ok" :value "(Mask {.bits 255 .name \"all\"})")))
|
(lambda (_) '(:status "ok" :value "(Mask {.bits 255 .name \"all\"})")))
|
||||||
|
|||||||
27
lib/dev.ml
27
lib/dev.ml
@ -2376,13 +2376,26 @@ let inspect t ~frame ~slot ~path =
|
|||||||
| Ok (c, label, ty) ->
|
| Ok (c, label, ty) ->
|
||||||
(match run_render_thunk t ~tag:"i" ~c with
|
(match run_render_thunk t ~tag:"i" ~c with
|
||||||
| Error m -> error m
|
| Error m -> error m
|
||||||
| Ok v ->
|
| Ok out ->
|
||||||
(* One value and nothing else, so the whole of what came back is
|
(* The address the value is stored at, on a line of its own, and
|
||||||
it — minus the trailing newline the renderer does not write
|
then the value. See [Session.render_slot]. *)
|
||||||
here, because there is no second line to separate it from. *)
|
let addr, v =
|
||||||
|
match String.index_opt out '\n' with
|
||||||
|
| Some i ->
|
||||||
|
( Int64.of_string_opt (String.sub out 0 i),
|
||||||
|
String.sub out (i + 1) (String.length out - i - 1) )
|
||||||
|
| None -> (None, out)
|
||||||
|
in
|
||||||
ok
|
ok
|
||||||
[ ":frame " ^ Wire.quote name; ":name " ^ Wire.quote label;
|
([ ":frame " ^ Wire.quote name; ":name " ^ Wire.quote label;
|
||||||
":type " ^ Wire.quote ty; ":value " ^ Wire.quote v;
|
":type " ^ Wire.quote ty; ":value " ^ Wire.quote v ]
|
||||||
|
@ (match addr with
|
||||||
|
(* Unsigned, as an address is: a user-space pointer is
|
||||||
|
positive as an i64 today, and printing it as the
|
||||||
|
unsigned number keeps that true if it ever is not. *)
|
||||||
|
| Some a -> [ Printf.sprintf ":addr %Lu" a ]
|
||||||
|
| None -> [])
|
||||||
|
@ [
|
||||||
(* Which stop this was read at, so that a write built from
|
(* Which stop this was read at, so that a write built from
|
||||||
what is on the screen can name it and be refused if the
|
what is on the screen can name it and be refused if the
|
||||||
program has been round the loop since. Nothing about the
|
program has been round the loop since. Nothing about the
|
||||||
@ -2390,7 +2403,7 @@ let inspect t ~frame ~slot ~path =
|
|||||||
moment at which it is true of what the reader is looking
|
moment at which it is true of what the reader is looking
|
||||||
at, and an editor that asked for it separately would be
|
at, and an editor that asked for it separately would be
|
||||||
asking a second time about a different instant. *)
|
asking a second time about a different instant. *)
|
||||||
":at-stop " ^ string_of_int (Option.value ~default:0 (stop_gen t)) ]))
|
":at-stop " ^ string_of_int (Option.value ~default:0 (stop_gen t)) ])))
|
||||||
|
|
||||||
|
|
||||||
(* [(:op "set" :frame N :slot I :path (...) :edits (...) :at-stop G)] — the
|
(* [(:op "set" :frame N :slot I :path (...) :edits (...) :at-stop G)] — the
|
||||||
|
|||||||
@ -1681,6 +1681,29 @@ let render_slot ?(origin = "<inspect>") t ~frame ~(fn : Tast.fn) ~slot ~path
|
|||||||
(match Render.render c 0 v with
|
(match Render.render c 0 v with
|
||||||
| exception Loc.Error { Loc.dmsg = why; _ } -> Error (name ^ path_text path ^ ": " ^ why)
|
| exception Loc.Error { Loc.dmsg = why; _ } -> Error (name ^ path_text path ^ ": " ^ why)
|
||||||
| parts ->
|
| parts ->
|
||||||
|
(* Where the value lives, first and on a line of its own: [v] is
|
||||||
|
a place in the stopped frame, so its address is the storage
|
||||||
|
the listing is reading. The caller splits it off at the first
|
||||||
|
newline; a rendering has none, because [Render] quotes a
|
||||||
|
string's. *)
|
||||||
|
let addr =
|
||||||
|
{ Tast.e =
|
||||||
|
Tast.Prim
|
||||||
|
(Tast.Cast (Types.Int Types.I64),
|
||||||
|
[ { Tast.e = Tast.Prim (Tast.AddrOf, [ v ]);
|
||||||
|
ty = Types.Ptr v.Tast.ty; loc } ]);
|
||||||
|
ty = Types.Int Types.I64; loc }
|
||||||
|
in
|
||||||
|
let newline =
|
||||||
|
{ Tast.e =
|
||||||
|
Tast.Prim
|
||||||
|
(Tast.Bytes, [ { Tast.e = Tast.Str "\n"; ty = Types.String; loc } ]);
|
||||||
|
ty = Types.Slice (Types.Int Types.U8); loc }
|
||||||
|
in
|
||||||
|
let parts =
|
||||||
|
dev_emitter.Render.ei64 addr :: dev_emitter.Render.ebytes newline
|
||||||
|
:: parts
|
||||||
|
in
|
||||||
let nullary n = { Tast.e = Tast.Call (n, []); ty = Types.Unit; loc } in
|
let nullary n = { Tast.e = Tast.Call (n, []); ty = Types.Unit; loc } in
|
||||||
t.thunks <- t.thunks + 1;
|
t.thunks <- t.thunks + 1;
|
||||||
let tname = Printf.sprintf "inspect/%d" t.thunks in
|
let tname = Printf.sprintf "inspect/%d" t.thunks in
|
||||||
|
|||||||
@ -2367,6 +2367,24 @@ let () =
|
|||||||
want "box" "(some)" "Point" "(Point {.x 4.5 .y 5.5})";
|
want "box" "(some)" "Point" "(Point {.x 4.5 .y 5.5})";
|
||||||
want "box" "(some \"x\")" "f32" "4.5";
|
want "box" "(some \"x\")" "f32" "4.5";
|
||||||
want "s" "(\"Shape.Rect.w\")" "i32" "3";
|
want "s" "(\"Shape.Rect.w\")" "i32" "3";
|
||||||
|
(* Where each value is stored. The struct and its first field share
|
||||||
|
an address and the second field is one f32 further on, so the
|
||||||
|
number is the layout's and not a label. *)
|
||||||
|
(match slot_of listing "mark" with
|
||||||
|
| None -> ()
|
||||||
|
| Some slot ->
|
||||||
|
let addr path =
|
||||||
|
match Wire.field (inspect ~path slot) "addr" with
|
||||||
|
| Some { Form.v = Form.Int a; _ } -> Some a
|
||||||
|
| _ -> None
|
||||||
|
in
|
||||||
|
(match addr "()", addr "(\"x\")", addr "(\"y\")" with
|
||||||
|
| Some m, Some x, Some y ->
|
||||||
|
if m <> x then
|
||||||
|
fail "mark is at %Ld and its first field at %Ld" m x;
|
||||||
|
if Int64.sub y x <> 4L then
|
||||||
|
fail "mark.y is %Ld bytes past mark.x, not 4" (Int64.sub y x)
|
||||||
|
| _ -> fail "inspect on a slot answered with no :addr"));
|
||||||
(* Emacs prints an empty list as `nil' and has no other spelling for
|
(* Emacs prints an empty list as `nil' and has no other spelling for
|
||||||
one, so a client in that language cannot send `()'. *)
|
one, so a client in that language cannot send `()'. *)
|
||||||
(match slot_of listing "mark" with
|
(match slot_of listing "mark" with
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user