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}