Skip to main content

vm/debugger/
recording.rs

1use std::collections::HashSet;
2use std::io;
3use std::path::Path;
4
5use crate::vm::{Program, Value, Vm, VmStatus};
6
7#[derive(Clone, Debug, PartialEq)]
8pub struct VmRecordingFrame {
9    pub ip: usize,
10    pub call_depth: usize,
11    pub stack: Vec<Value>,
12    pub locals: Vec<Value>,
13}
14
15#[derive(Clone, Debug)]
16pub struct VmRecording {
17    pub program: Program,
18    pub frames: Vec<VmRecordingFrame>,
19    pub terminal_status: Option<VmStatus>,
20}
21
22#[derive(Clone, Debug, Default)]
23pub struct VmRecordingReplayState {
24    pub cursor: usize,
25    pub offset_breakpoints: HashSet<usize>,
26    pub line_breakpoints: HashSet<u32>,
27}
28
29#[derive(Clone, Debug)]
30pub struct VmRecordingReplayResponse {
31    pub output: String,
32    pub current_line: Option<u32>,
33    pub at_end: bool,
34    pub exited: bool,
35}
36
37#[derive(Debug)]
38pub enum VmRecordingError {
39    Io(io::Error),
40    Wire(crate::vmbc::WireError),
41    InvalidFormat(&'static str),
42    Message(String),
43}
44
45pub(super) struct VmRecordingBuilder {
46    recording: VmRecording,
47}
48
49impl VmRecordingFrame {
50    pub(super) fn from_vm(vm: &Vm) -> Self {
51        Self {
52            ip: vm.ip(),
53            call_depth: vm.call_depth(),
54            stack: vm.stack().to_vec(),
55            locals: vm.locals().to_vec(),
56        }
57    }
58}
59
60impl VmRecordingBuilder {
61    pub(super) fn new(program: Program) -> Self {
62        Self {
63            recording: VmRecording {
64                program,
65                frames: Vec::new(),
66                terminal_status: None,
67            },
68        }
69    }
70
71    pub(super) fn record_state(&mut self, vm: &Vm) {
72        let frame = VmRecordingFrame::from_vm(vm);
73        if self.recording.frames.last() == Some(&frame) {
74            return;
75        }
76        self.recording.frames.push(frame);
77    }
78
79    pub(super) fn on_terminal_status(&mut self, vm: &Vm, status: VmStatus) {
80        self.record_state(vm);
81        self.recording.terminal_status = Some(status);
82    }
83
84    pub(super) fn finish(self) -> VmRecording {
85        self.recording
86    }
87}
88
89impl VmRecording {
90    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), VmRecordingError> {
91        let bytes = self.encode()?;
92        std::fs::write(path, bytes).map_err(VmRecordingError::Io)
93    }
94
95    pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, VmRecordingError> {
96        let bytes = std::fs::read(path).map_err(VmRecordingError::Io)?;
97        Self::decode(&bytes)
98    }
99
100    pub fn encode(&self) -> Result<Vec<u8>, VmRecordingError> {
101        const MAGIC: [u8; 4] = *b"PDRC";
102        const VERSION: u16 = 2;
103
104        let mut out = Vec::new();
105        out.extend_from_slice(&MAGIC);
106        out.extend_from_slice(&VERSION.to_le_bytes());
107
108        let program_bytes =
109            crate::vmbc::encode_program(&self.program).map_err(VmRecordingError::Wire)?;
110        write_u32_len(program_bytes.len(), &mut out)?;
111        out.extend_from_slice(&program_bytes);
112
113        let status_tag = match self.terminal_status {
114            Some(VmStatus::Halted) => 1u8,
115            Some(VmStatus::Yielded) => 2u8,
116            Some(VmStatus::Waiting(_)) => 3u8,
117            None => 0u8,
118        };
119        out.push(status_tag);
120        if let Some(VmStatus::Waiting(op_id)) = self.terminal_status {
121            out.extend_from_slice(&op_id.to_le_bytes());
122        }
123
124        write_u32_len(self.frames.len(), &mut out)?;
125        for frame in &self.frames {
126            write_u32_from_usize(frame.ip, &mut out)?;
127            write_u32_from_usize(frame.call_depth, &mut out)?;
128
129            write_u32_len(frame.stack.len(), &mut out)?;
130            for value in &frame.stack {
131                encode_value(value, &mut out)?;
132            }
133
134            write_u32_len(frame.locals.len(), &mut out)?;
135            for value in &frame.locals {
136                encode_value(value, &mut out)?;
137            }
138        }
139
140        Ok(out)
141    }
142
143    pub fn decode(bytes: &[u8]) -> Result<Self, VmRecordingError> {
144        const MAGIC: [u8; 4] = *b"PDRC";
145        const VERSION_LEGACY: u16 = 1;
146        const VERSION: u16 = 2;
147
148        let mut cursor = RecordingCursor::new(bytes);
149
150        let magic = cursor.read_exact(4)?;
151        if magic != MAGIC {
152            return Err(VmRecordingError::InvalidFormat("invalid recording magic"));
153        }
154
155        let version = cursor.read_u16()?;
156        if version != VERSION && version != VERSION_LEGACY {
157            return Err(VmRecordingError::Message(format!(
158                "unsupported recording version {version}"
159            )));
160        }
161
162        let program_len = cursor.read_u32()? as usize;
163        let program_bytes = cursor.read_exact(program_len)?;
164        let program = crate::vmbc::decode_program(program_bytes).map_err(VmRecordingError::Wire)?;
165
166        let terminal_status = match cursor.read_u8()? {
167            0 => None,
168            1 => Some(VmStatus::Halted),
169            2 => Some(VmStatus::Yielded),
170            3 if version >= VERSION => {
171                let op_id = cursor.read_u64()?;
172                Some(VmStatus::Waiting(op_id))
173            }
174            _ => {
175                return Err(VmRecordingError::InvalidFormat(
176                    "invalid terminal status tag",
177                ));
178            }
179        };
180
181        let frame_count = cursor.read_u32()? as usize;
182        let mut frames = Vec::with_capacity(frame_count);
183        for _ in 0..frame_count {
184            let ip = cursor.read_u32()? as usize;
185            let call_depth = cursor.read_u32()? as usize;
186
187            let stack_len = cursor.read_u32()? as usize;
188            let mut stack = Vec::with_capacity(stack_len);
189            for _ in 0..stack_len {
190                stack.push(decode_value(&mut cursor)?);
191            }
192
193            let locals_len = cursor.read_u32()? as usize;
194            let mut locals = Vec::with_capacity(locals_len);
195            for _ in 0..locals_len {
196                locals.push(decode_value(&mut cursor)?);
197            }
198
199            frames.push(VmRecordingFrame {
200                ip,
201                call_depth,
202                stack,
203                locals,
204            });
205        }
206
207        if !cursor.is_at_end() {
208            return Err(VmRecordingError::InvalidFormat(
209                "trailing bytes in recording payload",
210            ));
211        }
212
213        Ok(Self {
214            program,
215            frames,
216            terminal_status,
217        })
218    }
219}
220
221impl std::fmt::Display for VmRecordingError {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223        match self {
224            VmRecordingError::Io(err) => write!(f, "{err}"),
225            VmRecordingError::Wire(err) => write!(f, "{err}"),
226            VmRecordingError::InvalidFormat(message) => write!(f, "{message}"),
227            VmRecordingError::Message(message) => write!(f, "{message}"),
228        }
229    }
230}
231
232impl std::error::Error for VmRecordingError {}
233
234fn write_u32_len(len: usize, out: &mut Vec<u8>) -> Result<(), VmRecordingError> {
235    let value = u32::try_from(len)
236        .map_err(|_| VmRecordingError::Message(format!("length too large: {len}")))?;
237    out.extend_from_slice(&value.to_le_bytes());
238    Ok(())
239}
240
241fn write_u32_from_usize(value: usize, out: &mut Vec<u8>) -> Result<(), VmRecordingError> {
242    let value = u32::try_from(value)
243        .map_err(|_| VmRecordingError::Message(format!("value too large: {value}")))?;
244    out.extend_from_slice(&value.to_le_bytes());
245    Ok(())
246}
247
248fn encode_value(value: &Value, out: &mut Vec<u8>) -> Result<(), VmRecordingError> {
249    match value {
250        Value::Null => out.push(0),
251        Value::Int(value) => {
252            out.push(1);
253            out.extend_from_slice(&value.to_le_bytes());
254        }
255        Value::Float(value) => {
256            out.push(2);
257            out.extend_from_slice(&value.to_le_bytes());
258        }
259        Value::Bool(false) => out.push(3),
260        Value::Bool(true) => out.push(4),
261        Value::String(value) => {
262            out.push(5);
263            write_u32_len(value.len(), out)?;
264            out.extend_from_slice(value.as_bytes());
265        }
266        Value::Bytes(value) => {
267            out.push(6);
268            write_u32_len(value.len(), out)?;
269            out.extend_from_slice(value.as_slice());
270        }
271        Value::Array(values) => {
272            out.push(7);
273            write_u32_len(values.len(), out)?;
274            for value in values.iter() {
275                encode_value(value, out)?;
276            }
277        }
278        Value::Map(entries) => {
279            out.push(8);
280            write_u32_len(entries.len(), out)?;
281            for (key, value) in entries.iter() {
282                encode_value(key, out)?;
283                encode_value(value, out)?;
284            }
285        }
286    }
287    Ok(())
288}
289
290fn decode_value(cursor: &mut RecordingCursor<'_>) -> Result<Value, VmRecordingError> {
291    match cursor.read_u8()? {
292        0 => Ok(Value::Null),
293        1 => Ok(Value::Int(cursor.read_i64()?)),
294        2 => Ok(Value::Float(cursor.read_f64()?)),
295        3 => Ok(Value::Bool(false)),
296        4 => Ok(Value::Bool(true)),
297        5 => {
298            let len = cursor.read_u32()? as usize;
299            let bytes = cursor.read_exact(len)?;
300            let value = std::str::from_utf8(bytes)
301                .map_err(|_| VmRecordingError::InvalidFormat("invalid utf-8 string"))?;
302            Ok(Value::string(value))
303        }
304        6 => {
305            let len = cursor.read_u32()? as usize;
306            Ok(Value::bytes(cursor.read_exact(len)?.to_vec()))
307        }
308        7 => {
309            let len = cursor.read_u32()? as usize;
310            let mut values = Vec::with_capacity(len);
311            for _ in 0..len {
312                values.push(decode_value(cursor)?);
313            }
314            Ok(Value::Array(values.into()))
315        }
316        8 => {
317            let len = cursor.read_u32()? as usize;
318            let mut entries = Vec::with_capacity(len);
319            for _ in 0..len {
320                let key = decode_value(cursor)?;
321                let value = decode_value(cursor)?;
322                entries.push((key, value));
323            }
324            Ok(Value::map(entries))
325        }
326        _ => Err(VmRecordingError::InvalidFormat("invalid value tag")),
327    }
328}
329
330struct RecordingCursor<'a> {
331    bytes: &'a [u8],
332    offset: usize,
333}
334
335impl<'a> RecordingCursor<'a> {
336    fn new(bytes: &'a [u8]) -> Self {
337        Self { bytes, offset: 0 }
338    }
339
340    fn is_at_end(&self) -> bool {
341        self.offset == self.bytes.len()
342    }
343
344    fn read_exact(&mut self, len: usize) -> Result<&'a [u8], VmRecordingError> {
345        if self.offset + len > self.bytes.len() {
346            return Err(VmRecordingError::InvalidFormat(
347                "unexpected end of recording payload",
348            ));
349        }
350        let bytes = &self.bytes[self.offset..self.offset + len];
351        self.offset += len;
352        Ok(bytes)
353    }
354
355    fn read_u8(&mut self) -> Result<u8, VmRecordingError> {
356        Ok(self.read_exact(1)?[0])
357    }
358
359    fn read_u16(&mut self) -> Result<u16, VmRecordingError> {
360        let mut bytes = [0u8; 2];
361        bytes.copy_from_slice(self.read_exact(2)?);
362        Ok(u16::from_le_bytes(bytes))
363    }
364
365    fn read_u32(&mut self) -> Result<u32, VmRecordingError> {
366        let mut bytes = [0u8; 4];
367        bytes.copy_from_slice(self.read_exact(4)?);
368        Ok(u32::from_le_bytes(bytes))
369    }
370
371    fn read_u64(&mut self) -> Result<u64, VmRecordingError> {
372        let mut bytes = [0u8; 8];
373        bytes.copy_from_slice(self.read_exact(8)?);
374        Ok(u64::from_le_bytes(bytes))
375    }
376
377    fn read_i64(&mut self) -> Result<i64, VmRecordingError> {
378        let mut bytes = [0u8; 8];
379        bytes.copy_from_slice(self.read_exact(8)?);
380        Ok(i64::from_le_bytes(bytes))
381    }
382
383    fn read_f64(&mut self) -> Result<f64, VmRecordingError> {
384        let mut bytes = [0u8; 8];
385        bytes.copy_from_slice(self.read_exact(8)?);
386        Ok(f64::from_le_bytes(bytes))
387    }
388}