pub struct Stop<'m> { /* private fields */ }Expand description
One instruction’s worth of standing still.
It borrows the machine for the length of the call and nothing longer, which is the whole of why the call is inverted. Every method answers with an owned snapshot, so a debugger keeps what it asks for and holds nothing of the machine.
The frame’s pc is truthful here: the dispatch loop syncs it before
asking, which it does not do between instructions, and a view built
anywhere else would name the instruction after the one being stopped at.
Implementations§
Source§impl<'m> Stop<'m>
impl<'m> Stop<'m>
Sourcepub fn instructions(&self) -> u64
pub fn instructions(&self) -> u64
How many instructions this task has run, this one included.
The count crate::Vm::instructions reports, read at the moment the
instruction about to run was counted. It is this task’s, and
Stop::task is what says whose.
Sourcepub fn allocated_words(&self) -> u64
pub fn allocated_words(&self) -> u64
Words this task’s heap has handed out, reuse counted each time.
Read at the moment the instruction about to run was counted, so the
difference between two consecutive stops is what the instruction
between them allocated. That is how crate::vm::profile::Profiler
attributes a heap to the code that asked for it: there is no seam
inside Memory::alloc that knows which instruction it is serving, and
a difference needs none.
Sourcepub fn allocations(&self) -> u64
pub fn allocations(&self) -> u64
Objects this task’s heap has handed out, reuse counted each time.
Beside Stop::allocated_words because allocating often and
allocating large are different faults with different repairs.
Sourcepub fn task(&self) -> u64
pub fn task(&self) -> u64
Which task this stop is in.
Everything else a Stop answers is one task’s — the count, the
depth, the frames — because a spawned task runs on a machine of its
own, and until this was here a debugger could not tell two of them
apart. A policy stated in frame depth is then a policy that a second
task can satisfy by accident: a step asked in the entry, finished
by an instruction of a task the entry spawned.
The number is crate::ENTRY_TASK for the entry and a spawned
task’s own id otherwise, which is to say it is the number
crate::trace’s events carry under task. That is deliberate and it
is the whole of the choice here: a debugger and a trace of the same
run must name the same task the same way, or a person holding both
has to work out the correspondence themselves.
It is opaque. Two stops with the same id are the same task and two with different ids are not; nothing else about the number is promised, and no ordering of it means anything.
Sourcepub fn function(&self) -> String
pub fn function(&self) -> String
module.name of the function this stop is in.
The function the source would say it is in, which is not always the
one whose frame the machine pushed: an instruction inside a body
lower::inline expanded belongs to the body that was written, and a
session that named the caller would tell a person their breakpoint had
stopped somewhere they had not asked about. Function::inlined is what
says otherwise, and Stop::function_id is deliberately the other
answer.
A String, built here, because that is what a session prints and a
name is what a person reads. A caller that will look the function up
again — a profiler counting instructions, above all — wants
Stop::function_id instead: this allocates, and a debugger that
stops at every instruction would allocate at every instruction.
Sourcepub fn function_id(&self) -> FunctionId
pub fn function_id(&self) -> FunctionId
Which function this stop is in, as the program names it.
The identity rather than the name: two stops in one function answer
the same id, and an id indexes Program::functions — so a caller can
hold one per stop without holding a string per stop.
It is the machine’s answer and not the source’s: an instruction of an
expanded body reports the function whose frame and whose code hold it,
where Stop::function reports the body that was written. A profiler
keys a count by this and prints function+pc, and a pc is a counter of
the function this names; naming the callee there would make the pair
disagree.
Sourcepub fn pc(&self) -> u32
pub fn pc(&self) -> u32
Which instruction of that function is about to run.
A counter of the function whose code holds it, which for an
instruction inside an expanded body is the caller’s rather than the
body’s: lower::inline wrote the body there and there is no other
numbering. So a stop reported as m.inner at pc 1 is not m.inner’s
second instruction, it is m.inner’s first, standing at m.outer’s
counter 1. Stop::code numbers the same way, which is what keeps a
listing and the pc beside it agreeing.
Sourcepub fn depth(&self) -> usize
pub fn depth(&self) -> usize
How many calls are live, this one included.
An expanded body counts. lower::inline writes a small leaf’s
instructions into its caller’s code and pushes no frame for them, and
a debugger that counted only the frames the machine pushed would say a
stop inside such a body was a stop in the caller — finish would run
past the body it was asked to finish, next would step over nothing,
and a backtrace would be one name short. Function::inlined is what
says otherwise, and this is the one place the count comes from.
Counted rather than built, because State::wanted asks this at every
stop of a stepping session and the frames themselves are only wanted
when something is shown.
Sourcepub fn backtrace(&self) -> Vec<Call>
pub fn backtrace(&self) -> Vec<Call>
Every live call, innermost first.
This renders every local of every frame — and every word of every
local — so a debugger that stops at every instruction and takes a
whole backtrace at each one is doing real work per instruction.
Stop::frame is the same view of one call, for a session that only
shows what it was asked for.
Sourcepub fn frame(&self, at: usize) -> Option<Call>
pub fn frame(&self, at: usize) -> Option<Call>
The call at levels out from this one, or None past the outermost.
A frame that is not the innermost is suspended at the instruction
after the call that led one level deeper, and that resume address is
not where the frame is. The instruction it names is whatever runs
next — frequently the next statement’s, or the return the body ends
with — so a line built from it points away from the call as often as
at it, and a name the call site had in scope may already have gone out
of it. - 1 is always the call itself, because a pc is only ever
synced after the instruction it dispatched.
This is the rule Machine::calls’s own documentation states for the
error chain, which had made the same choice for the same reason and
left this view alone. Issue #302 is what settled that the two should
agree: with the copy after a call gone, the resume address in a
one-expression body is the return, and a suspended frame answered
that its caller’s locals were out of scope.
The innermost frame keeps its own pc. It is not suspended at a
resume address — the machine is about to execute the instruction it
names — so there is nothing to look back past.
Sourcepub fn object(&self, at: u64) -> Option<Object>
pub fn object(&self, at: u64) -> Option<Object>
What the word at names, if it names an object of this run’s heap.
The one place a raw word crosses, and it crosses inward: what comes
back is a rendered snapshot. It is for the view VM development wants —
a Word showed an address, and this says what is there — and it
promises nothing about what a word means. A word that is not an
object this memory holds answers None.
Sourcepub fn code(&self, at: usize, reach: usize) -> Vec<Line>
pub fn code(&self, at: usize, reach: usize) -> Vec<Line>
The instructions around frame at’s pc, reach either side of it,
or nothing past the outermost frame.
The disassembly a session shows beside a stop.
cove_ir::print::one renders each, which is the same rendering
cove ir prints, so a debugger and a dump do not disagree about what
an instruction is called.
at names a frame the way Stop::frame names one, and for the
same reason: a session that lets a person select a frame has to be
able to show that frame’s code, and one that could only ever
disassemble the innermost would answer frame 2 with frame 0’s
instructions. It is a parameter here rather than a method on Call
because a Call is an owned snapshot: giving it a disassembly would
mean rendering every instruction of every live function at every
stop, and a backtrace is already the expensive view.
The pc it reads is the one Stop::frame reports, which for a
suspended frame is the call it is waiting on rather than the resume
address after it. The two panes of a session are one view: a
backtrace naming a line and a disassembly marking a different
instruction would be the debugger disagreeing with itself.