Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,11 @@ and `program::operand`. Module construction and string interning happen before
execution. CPU computes with typed operands and returns effects; it owns no stack,
frames, or PC. Its heap exchanges operands through the same stack protocols.
HeapAlloc and HeapPush consume available operands sequentially on underflow.
Print belongs to a separate component; no Span or Breakpoint handling is added.
Print belongs to a separate component. `breakpoint` suspends the invocation after
that instruction: `execute`/`resume` return `Ok(())` with `is_paused()` set, and
the next `resume` continues at the following instruction. `span file start end`
records a source span on the current frame, readable through `span()`; each call
starts at `(0, 0, 0)` and returning restores the caller's span.
Feature-disabled legacy and typed CPU behavior is retained.

For variable input counts, use `PopFromStack<StackBatch<T>>` and implement
Expand Down
3 changes: 3 additions & 0 deletions crates/vihaco-stdlib/src/execution_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub struct ExecutionState {
frames: Vec<Frame>,
pc: u32,
halted: bool,
paused: bool,
executing: bool,
}

Expand All @@ -113,6 +114,7 @@ impl Default for ExecutionState {
frames: Vec::new(),
pc: 0,
halted: true,
paused: false,
executing: false,
}
}
Expand All @@ -131,6 +133,7 @@ impl Clone for ExecutionState {
frames: self.frames.clone(),
pc: self.pc,
halted: self.halted,
paused: self.paused,
executing: false,
}
}
Expand Down
31 changes: 31 additions & 0 deletions crates/vihaco-stdlib/src/execution_state/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,37 @@ impl Execute<program::Halt> for ExecutionState {
}
}

impl Execute<program::Breakpoint> for ExecutionState {
type Message = NoMessage;
type Effect = ();
type Error = eyre::Report;

#[inline]
fn execute(&mut self, _instruction: &program::Breakpoint, _: NoMessage) -> eyre::Result<()> {
self.ensure_running()?;
// The fetched continuation is retained, so resuming skips the breakpoint.
self.paused = true;
Ok(())
}
}

impl Execute<program::Span> for ExecutionState {
type Message = NoMessage;
type Effect = ();
type Error = eyre::Report;

#[inline]
fn execute(&mut self, instruction: &program::Span, _: NoMessage) -> eyre::Result<()> {
self.ensure_running()?;
let frame = self
.frames
.last_mut()
.ok_or_else(|| eyre::eyre!("no current frame"))?;
frame.span = (instruction.0, instruction.1, instruction.2);
Ok(())
}
}

impl Execute<program::Branch> for ExecutionState {
type Message = BranchMessage;
type Effect = ();
Expand Down
10 changes: 9 additions & 1 deletion crates/vihaco-stdlib/src/execution_state/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ where
resume(module, machine)
}

/// Continue an invocation until Halt/root return or an error.
/// Continue an invocation until Halt/root return, a breakpoint, or an error.
///
/// Fetch establishes the sequential continuation before the Step runs. Program
/// instructions can replace that continuation. A fault or terminal instruction
/// leaves its original position available through [`ExecutionState::pc()`].
///
/// A `breakpoint` returns `Ok(())` with [`ExecutionState::is_paused()`] set and
/// the PC at the following instruction. Calling `resume` again clears the pause
/// and continues from there.
///
/// The caller must supply the same unchanged module used to start the active
/// invocation. This checked API validates accesses, but does not enforce module
/// identity. After an error, earlier operand consumption and effects remain;
Expand All @@ -56,6 +60,7 @@ where
"program execution is already in progress"
);
program.executing = true;
program.paused = false;
// Capture code once from the independent module borrow.
let code = &module.code;
let result = (|| {
Expand All @@ -76,6 +81,9 @@ where
program.pc = position;
}
result?;
if program.paused {
break;
}
}
Ok(())
})();
Expand Down
22 changes: 20 additions & 2 deletions crates/vihaco-stdlib/src/execution_state/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ impl ExecutionState {
self.halted
}

/// True after a `breakpoint` suspended a still-active invocation.
///
/// [`resume`](super::resume) clears the flag and continues at [`Self::pc()`],
/// the instruction following the breakpoint. A paused invocation is not
/// halted: it cannot be restarted or replaced until it finishes or is reset.
pub fn is_paused(&self) -> bool {
self.paused
}

/// Source span `(file, start, end)` most recently recorded by `span` in the
/// current frame, or `None` without an active frame.
///
/// Each call starts with `(0, 0, 0)`; returning restores the caller's span.
pub fn span(&self) -> Option<(u32, u32, u32)> {
self.frames.last().map(|frame| frame.span)
}

/// Enter main using its module metadata and host-supplied arguments.
///
/// Entry creates the outermost frame, zeroes non-argument locals, selects
Expand Down Expand Up @@ -59,14 +76,14 @@ impl ExecutionState {
self.frames.push(Frame {
base: 0,
local_count: locals,
// Frame's shared record retains this legacy field; this path does
// not track source spans or implement a Span instruction.
// Recorded by the `span` instruction for the frame it executes in.
span: (0, 0, 0),
function: Some(function as usize),
ret_pc: 0,
});
self.pc = target;
self.halted = false;
self.paused = false;
Ok(())
}

Expand Down Expand Up @@ -133,6 +150,7 @@ impl Reset for ExecutionState {
self.frames.clear();
self.pc = 0;
self.halted = true;
self.paused = false;
}
}

Expand Down
8 changes: 6 additions & 2 deletions crates/vihaco-stdlib/src/loadable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ where
///
/// # Errors
///
/// Rejects replacement while an invocation is active, including one that
/// could resume after an execution error. Call [`Self::reset`] to abandon
/// Rejects replacement while an invocation is active, including one paused
/// at a breakpoint or one that could resume after an execution error. Call [`Self::reset`] to abandon
/// that invocation first. Rejection leaves the current image and execution
/// state unchanged.
pub fn replace_image(&mut self, image: Image<Inst, V, Ty, Info>) -> eyre::Result<()> {
Expand Down Expand Up @@ -107,6 +107,10 @@ where

/// Continue the existing invocation against the same paired image.
///
/// This also continues past a `breakpoint`; check
/// [`ExecutionState::is_paused`] after either call to tell a pause apart
/// from completion.
///
/// # Errors
///
/// Returns the runner's state, instruction-fetch, or dispatch errors without
Expand Down
10 changes: 10 additions & 0 deletions crates/vihaco-stdlib/src/program/dialect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ pub mod program {
#[pattern = "'halt"]
pub struct Halt;

/// Suspend the invocation after this instruction; `resume` continues it.
#[instruction]
#[pattern = "'breakpoint"]
pub struct Breakpoint;

/// Record the current frame's source span as `(file, start, end)`.
#[instruction]
#[pattern = "'span $0 $1 $2"]
pub struct Span(pub u32, pub u32, pub u32);

#[instruction]
#[pattern = "'load_i32 $0"]
pub struct LoadI32(pub u32);
Expand Down
71 changes: 71 additions & 0 deletions crates/vihaco-stdlib/tests/execution_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,77 @@ fn recursive_execution_preserves_continuations_and_multiple_results() {
assert_eq!(vm.program.pc(), 3);
}

#[test]
fn breakpoint_pauses_and_resume_continues_after_it() {
let (module, mut vm) = machine(&[
"program::program::const_u64 20",
"program::program::breakpoint",
"program::program::const_u64 22",
"cpu::cpu::add_u64",
"program::program::breakpoint",
"program::program::ret 1",
]);
execute(&module, &mut vm, &[]).unwrap();
assert!(vm.program.is_paused());
assert!(!vm.program.is_halted());
assert_eq!(vm.program.pc(), 2);
assert_eq!(vm.program.stack(), &[0, 20]);
// A paused invocation is still active.
assert!(execute(&module, &mut vm, &[]).is_err());
resume(&module, &mut vm).unwrap();
assert!(vm.program.is_paused());
assert_eq!(vm.program.pc(), 5);
assert_eq!(vm.program.stack(), &[0, 42]);
resume(&module, &mut vm).unwrap();
assert!(!vm.program.is_paused());
assert!(vm.program.is_halted());
assert_eq!(vm.program.stack(), &[42]);
// Reset clears a pause so the invocation can be restarted.
execute(&module, &mut vm, &[]).unwrap();
assert!(vm.program.is_paused());
vm.program.reset();
assert!(!vm.program.is_paused());
execute(&module, &mut vm, &[]).unwrap();
assert_eq!(vm.program.pc(), 2);
}

#[test]
fn span_is_recorded_per_frame_and_restored_on_return() {
let (mut module, mut vm) = machine(&[
"program::program::span 1 10 20",
"program::program::call 0, 4",
"program::program::breakpoint",
"program::program::ret 0",
"program::program::breakpoint",
"program::program::span 2 30 40",
"program::program::breakpoint",
"program::program::ret 0",
]);
module.functions[0].local_count = 0;
module.functions[0].end_address = 4;
module.functions.push(FunctionInfo {
name: 0,
signature: Signature {
params: vec![],
ret: vec![],
},
local_count: 0,
start_address: 4,
end_address: 8,
file: 0,
});
assert_eq!(vm.program.span(), None);
execute(&module, &mut vm, &[]).unwrap();
assert_eq!(vm.program.span(), Some((0, 0, 0))); // Fresh callee frame.
resume(&module, &mut vm).unwrap();
assert_eq!(vm.program.span(), Some((2, 30, 40)));
resume(&module, &mut vm).unwrap();
assert_eq!(vm.program.span(), Some((1, 10, 20))); // Caller's span restored.
resume(&module, &mut vm).unwrap();
assert!(vm.program.is_halted());
assert_eq!(vm.program.span(), None);
}

#[test]
fn execution_failures_preserve_consumption_and_faulting_pc() {
let (module, mut vm) = machine(&[
Expand Down
Loading