javm_exec/ecall.rs
1//! `EcallHandler` trait: how the execution engine dispatches ecalls
2//! to the integration layer.
3//!
4//! Per architecture: the engine knows there are ecalls and that each
5//! carries a kind (PVM `ecall` opcode 3 with no immediate, vs PVM
6//! `ecalli` opcode 10 with a u32 immediate). It doesn't know what the
7//! kind *means*. The caller supplies an `EcallHandler` that
8//! interprets ecalls as MGMT operations, host-call selectors, CALL /
9//! HALT / yield transfers, etc.
10//!
11//! The handler may either:
12//!
13//! - Return `Continue` — engine continues at the current PC
14//! (already advanced past the ecall instruction before the handler
15//! runs). Used for purely-stateful ecalls (MGMT_COPY, MGMT_MOVE,
16//! etc.) that just mutate `regs` / `mem` and resume.
17//!
18//! - Return `Exit(reason)` — engine returns this `ExitReason` from
19//! `execute()`. Used for control-flow ecalls (HALT, yield, CALL
20//! into another Instance) that require the integration layer.
21
22use crate::exit::ExitReason;
23use crate::mem::Memory;
24use crate::regs::Regs;
25
26/// Which PVM ecall opcode triggered this invocation.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum EcallKind {
29 /// PVM `ecall` (opcode 3). No immediate; the handler reads
30 /// `regs[11]` (mgmt op) and `regs[12]` (subject|object) per the
31 /// v3 ABI convention.
32 Ecall,
33 /// PVM `ecalli` (opcode 10). Carries a u32 immediate payload.
34 Ecalli(u32),
35}
36
37/// Result of handling one ecall.
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub enum EcallResult {
40 /// Engine continues at the current PC (advanced past the ecall).
41 Continue,
42 /// Engine exits with the given reason.
43 Exit(ExitReason),
44}
45
46/// Trait the integration layer implements to interpret ecalls.
47///
48/// PC has been advanced past the instruction by the engine; the
49/// handler operates on the post-advance register/memory state.
50pub trait EcallHandler {
51 fn handle(&mut self, kind: EcallKind, regs: &mut Regs, mem: &mut dyn Memory) -> EcallResult;
52}
53
54/// A no-op handler: every ecall exits with `Panic`. Useful as a
55/// default for tests where the engine isn't supposed to encounter
56/// ecalls.
57#[derive(Debug, Default)]
58pub struct PanickingHandler;
59
60impl EcallHandler for PanickingHandler {
61 fn handle(&mut self, _kind: EcallKind, _regs: &mut Regs, _mem: &mut dyn Memory) -> EcallResult {
62 EcallResult::Exit(ExitReason::Panic)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::mem::Mem;
70
71 #[test]
72 fn panicking_handler_always_exits_panic() {
73 let mut h = PanickingHandler;
74 let mut regs = Regs::new();
75 let mut mem = Mem::new();
76 assert_eq!(
77 h.handle(EcallKind::Ecall, &mut regs, &mut mem),
78 EcallResult::Exit(ExitReason::Panic)
79 );
80 assert_eq!(
81 h.handle(EcallKind::Ecalli(42), &mut regs, &mut mem),
82 EcallResult::Exit(ExitReason::Panic)
83 );
84 }
85
86 /// A handler that increments φ₀ on every ecall (any kind).
87 struct CountingHandler {
88 count: u32,
89 }
90 impl EcallHandler for CountingHandler {
91 fn handle(
92 &mut self,
93 _kind: EcallKind,
94 regs: &mut Regs,
95 _mem: &mut dyn Memory,
96 ) -> EcallResult {
97 self.count += 1;
98 regs.write(0, regs.read(0).wrapping_add(1));
99 EcallResult::Continue
100 }
101 }
102
103 #[test]
104 fn counting_handler_mutates_regs_and_continues() {
105 let mut h = CountingHandler { count: 0 };
106 let mut regs = Regs::new();
107 let mut mem = Mem::new();
108 assert_eq!(
109 h.handle(EcallKind::Ecall, &mut regs, &mut mem),
110 EcallResult::Continue
111 );
112 assert_eq!(regs.read(0), 1);
113 assert_eq!(
114 h.handle(EcallKind::Ecalli(7), &mut regs, &mut mem),
115 EcallResult::Continue
116 );
117 assert_eq!(regs.read(0), 2);
118 assert_eq!(h.count, 2);
119 }
120}