1use std::collections::{BTreeMap, HashMap, HashSet};
2use std::fmt::Write;
3
4use crate::builtins::BuiltinFunction;
5use crate::bytecode::{TypeMap, ValueType};
6use crate::compiler::ir::TypeSchema;
7use crate::debug_info::{ArgInfo, DebugFunction, DebugInfo, LineInfo, LocalInfo};
8use crate::vm::{HostImport, OpCode, Program, Value};
9
10const MAGIC: [u8; 4] = *b"VMBC";
11const VERSION_V2: u16 = 2;
12const VERSION_V3: u16 = 3;
13const VERSION_V4: u16 = 4;
14const VERSION_V5: u16 = 5;
15const VERSION_V6: u16 = 6;
16const VERSION_V8: u16 = 8;
17const ENCODE_VERSION: u16 = VERSION_V8;
18const FLAGS: u16 = 0;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum WireError {
22 UnexpectedEof,
23 InvalidMagic([u8; 4]),
24 UnsupportedVersion(u16),
25 UnsupportedFlags(u16),
26 InvalidConstantTag(u8),
27 InvalidBool(u8),
28 InvalidTypeMapFlag(u8),
29 InvalidDebugFlag(u8),
30 InvalidValueType(u8),
31 InvalidUtf8,
32 StringTooLong(usize),
33 CodeTooLong(usize),
34 UnsupportedConstantType(&'static str),
35 LengthTooLarge(&'static str, usize),
36 TrailingBytes,
37}
38
39impl std::fmt::Display for WireError {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 WireError::UnexpectedEof => write!(f, "unexpected end of input"),
43 WireError::InvalidMagic(found) => write!(f, "invalid magic: {found:?}"),
44 WireError::UnsupportedVersion(version) => {
45 write!(f, "unsupported version: {version}")
46 }
47 WireError::UnsupportedFlags(flags) => write!(f, "unsupported flags: {flags}"),
48 WireError::InvalidConstantTag(tag) => write!(f, "invalid constant tag: {tag}"),
49 WireError::InvalidBool(value) => write!(f, "invalid bool value: {value}"),
50 WireError::InvalidTypeMapFlag(value) => write!(f, "invalid type-map flag: {value}"),
51 WireError::InvalidDebugFlag(value) => write!(f, "invalid debug flag: {value}"),
52 WireError::InvalidValueType(value) => write!(f, "invalid value type: {value}"),
53 WireError::InvalidUtf8 => write!(f, "invalid utf-8 string"),
54 WireError::StringTooLong(len) => write!(f, "string too long: {len}"),
55 WireError::CodeTooLong(len) => write!(f, "code too long: {len}"),
56 WireError::UnsupportedConstantType(kind) => {
57 write!(f, "unsupported constant type for wire format: {kind}")
58 }
59 WireError::LengthTooLarge(field, len) => {
60 write!(f, "{field} length too large: {len}")
61 }
62 WireError::TrailingBytes => write!(f, "trailing bytes after program payload"),
63 }
64 }
65}
66
67impl std::error::Error for WireError {}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum ValidationError {
71 TruncatedOperand {
72 offset: usize,
73 opcode: u8,
74 expected_bytes: usize,
75 },
76 InvalidOpcode {
77 offset: usize,
78 opcode: u8,
79 },
80 InvalidConstant {
81 offset: usize,
82 index: u32,
83 },
84 InvalidCall {
85 offset: usize,
86 index: u16,
87 },
88 InvalidCallArity {
89 offset: usize,
90 index: u16,
91 expected: u8,
92 got: u8,
93 },
94 InvalidJumpTarget {
95 offset: usize,
96 target: u32,
97 },
98}
99
100impl std::fmt::Display for ValidationError {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self {
103 ValidationError::TruncatedOperand {
104 offset,
105 opcode,
106 expected_bytes,
107 } => write!(
108 f,
109 "truncated operand at offset {offset} for opcode {opcode:#04x}, expected {expected_bytes} bytes",
110 ),
111 ValidationError::InvalidOpcode { offset, opcode } => {
112 write!(f, "invalid opcode {opcode:#04x} at offset {offset}")
113 }
114 ValidationError::InvalidConstant { offset, index } => write!(
115 f,
116 "invalid constant index {index} for ldc instruction at offset {offset}",
117 ),
118 ValidationError::InvalidCall { offset, index } => {
119 write!(f, "invalid call index {index} at offset {offset}")
120 }
121 ValidationError::InvalidCallArity {
122 offset,
123 index,
124 expected,
125 got,
126 } => write!(
127 f,
128 "invalid call arity {got} for import index {index} at offset {offset}, expected {expected}",
129 ),
130 ValidationError::InvalidJumpTarget { offset, target } => write!(
131 f,
132 "invalid jump target {target} referenced by instruction at offset {offset}",
133 ),
134 }
135 }
136}
137
138impl std::error::Error for ValidationError {}
139
140pub fn encode_program(program: &Program) -> Result<Vec<u8>, WireError> {
141 let mut out = Vec::new();
142 out.extend_from_slice(&MAGIC);
143 out.extend_from_slice(&ENCODE_VERSION.to_le_bytes());
144 out.extend_from_slice(&FLAGS.to_le_bytes());
145 write_u32_count("constants", program.constants.len(), &mut out)?;
146
147 for constant in &program.constants {
148 match constant {
149 Value::Null => {
150 out.push(4);
151 }
152 Value::Int(value) => {
153 out.push(0);
154 out.extend_from_slice(&value.to_le_bytes());
155 }
156 Value::Float(value) => {
157 out.push(3);
158 out.extend_from_slice(&value.to_le_bytes());
159 }
160 Value::Bool(value) => {
161 out.push(1);
162 out.push(u8::from(*value));
163 }
164 Value::String(value) => {
165 out.push(2);
166 write_u32_len("constant string", value.len(), &mut out)?;
167 out.extend_from_slice(value.as_bytes());
168 }
169 Value::Bytes(value) => {
170 out.push(5);
171 write_u32_len("constant bytes", value.len(), &mut out)?;
172 out.extend_from_slice(value.as_slice());
173 }
174 Value::Array(_) => {
175 return Err(WireError::UnsupportedConstantType("array"));
176 }
177 Value::Map(_) => {
178 return Err(WireError::UnsupportedConstantType("map"));
179 }
180 }
181 }
182
183 write_u32_len("code", program.code.len(), &mut out)?;
184 out.extend_from_slice(&program.code);
185
186 if ENCODE_VERSION >= VERSION_V4 {
187 write_u32_count("imports", program.imports.len(), &mut out)?;
188 for import in &program.imports {
189 write_string("import name", &import.name, &mut out)?;
190 out.push(import.arity);
191 out.push(import.return_type as u8);
192 }
193 }
194
195 if ENCODE_VERSION >= VERSION_V6 {
196 write_type_map(&mut out, program.type_map.as_ref())?;
197 }
198
199 if ENCODE_VERSION >= VERSION_V2 {
200 write_debug_info(&mut out, program.debug.as_ref())?;
201 }
202
203 Ok(out)
204}
205
206pub fn decode_program(bytes: &[u8]) -> Result<Program, WireError> {
207 let mut cursor = Cursor::new(bytes);
208
209 let magic = cursor.read_exact_array::<4>()?;
210 if magic != MAGIC {
211 return Err(WireError::InvalidMagic(magic));
212 }
213
214 let version = cursor.read_u16()?;
215 if version != VERSION_V8 {
216 return Err(WireError::UnsupportedVersion(version));
217 }
218
219 let flags = cursor.read_u16()?;
220 if flags != FLAGS {
221 return Err(WireError::UnsupportedFlags(flags));
222 }
223
224 let constant_count = cursor.read_u32()? as usize;
225 let mut constants = Vec::with_capacity(constant_count);
226 for _ in 0..constant_count {
227 let tag = cursor.read_u8()?;
228 let value = match tag {
229 4 => Value::Null,
230 0 => Value::Int(cursor.read_i64()?),
231 3 => Value::Float(cursor.read_f64()?),
232 1 => {
233 let raw = cursor.read_u8()?;
234 match raw {
235 0 => Value::Bool(false),
236 1 => Value::Bool(true),
237 other => return Err(WireError::InvalidBool(other)),
238 }
239 }
240 2 => {
241 let len = cursor.read_u32()? as usize;
242 let text_bytes = cursor.read_exact(len)?;
243 let text =
244 String::from_utf8(text_bytes.to_vec()).map_err(|_| WireError::InvalidUtf8)?;
245 Value::string(text)
246 }
247 5 => {
248 let len = cursor.read_u32()? as usize;
249 Value::bytes(cursor.read_exact(len)?.to_vec())
250 }
251 other => return Err(WireError::InvalidConstantTag(other)),
252 };
253 constants.push(value);
254 }
255
256 let code_len = cursor.read_u32()? as usize;
257 let code = cursor.read_exact(code_len)?.to_vec();
258 let import_count = cursor.read_u32()? as usize;
259 let mut imports = Vec::with_capacity(import_count);
260 for _ in 0..import_count {
261 imports.push(HostImport {
262 name: cursor.read_string()?,
263 arity: cursor.read_u8()?,
264 return_type: read_value_type(cursor.read_u8()?)?,
265 });
266 }
267 let type_map = if version >= VERSION_V6 {
268 read_type_map(&mut cursor)?
269 } else {
270 None
271 };
272 let debug = if version >= VERSION_V2 {
273 read_debug_info(&mut cursor, version)?
274 } else {
275 None
276 };
277
278 if !cursor.is_eof() {
279 return Err(WireError::TrailingBytes);
280 }
281
282 let mut program = Program::with_imports_and_debug(constants, code, imports, debug);
283 program.type_map = type_map;
284 Ok(program)
285}
286
287pub fn validate_program(program: &Program, host_fn_count: u16) -> Result<(), ValidationError> {
288 analyze_program(program, Some(host_fn_count)).map(|_| ())
289}
290
291pub fn infer_local_count(program: &Program) -> Result<usize, ValidationError> {
292 let analysis = analyze_program(program, None)?;
293 Ok(match analysis.max_local_index {
294 Some(index) => index as usize + 1,
295 None => 0,
296 })
297}
298
299#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
300pub struct DisassembleOptions {
301 pub show_source: bool,
302}
303
304pub fn disassemble_vmbc(bytes: &[u8]) -> Result<String, WireError> {
305 disassemble_vmbc_with_options(bytes, DisassembleOptions::default())
306}
307
308pub fn disassemble_vmbc_with_options(
309 bytes: &[u8],
310 options: DisassembleOptions,
311) -> Result<String, WireError> {
312 let program = decode_program(bytes)?;
313 Ok(disassemble_program_with_options(&program, options))
314}
315
316pub fn disassemble_program(program: &Program) -> String {
317 disassemble_program_with_options(program, DisassembleOptions::default())
318}
319
320pub fn disassemble_program_with_options(program: &Program, options: DisassembleOptions) -> String {
321 let mut out = String::new();
322 let _ = writeln!(&mut out, "constants ({}):", program.constants.len());
323 for (index, constant) in program.constants.iter().enumerate() {
324 let _ = writeln!(&mut out, " [{index:04}] {constant:?}");
325 }
326
327 let _ = writeln!(&mut out, "imports ({}):", program.imports.len());
328 for (index, import) in program.imports.iter().enumerate() {
329 let _ = writeln!(&mut out, " [{index:04}] {}/{}", import.name, import.arity);
330 }
331 let _ = writeln!(&mut out, "code ({} bytes):", program.code.len());
332 let mut source_annotations = source_annotations(program, options.show_source);
333 if options.show_source && source_annotations.is_none() {
334 let _ = writeln!(&mut out, " ; source: <none>");
335 }
336 let code = &program.code;
337 let mut ip = 0usize;
338 while ip < code.len() {
339 let start = ip;
340 if let Some(lines_at_offset) = source_annotations
341 .as_mut()
342 .and_then(|annotations| annotations.remove(&start))
343 {
344 for (line, text) in lines_at_offset {
345 let _ = writeln!(&mut out, " ; src {line:04} {text}");
346 }
347 }
348 let opcode = code[ip];
349 ip += 1;
350
351 let mut instruction = String::new();
352 let mut truncated = false;
353 match opcode {
354 x if x == OpCode::Nop as u8 => instruction.push_str("nop"),
355 x if x == OpCode::Ret as u8 => instruction.push_str("ret"),
356 x if x == OpCode::Ldc as u8 => {
357 if let Some(index) = read_u32(code, &mut ip) {
358 instruction.push_str(&format!("ldc {index}"));
359 if let Some(value) = program.constants.get(index as usize) {
360 instruction.push_str(&format!(" ; const[{index}]={value:?}"));
361 }
362 } else {
363 instruction.push_str("ldc <truncated>");
364 truncated = true;
365 }
366 }
367 x if x == OpCode::Add as u8 => instruction.push_str("add"),
368 x if x == OpCode::Sub as u8 => instruction.push_str("sub"),
369 x if x == OpCode::Mul as u8 => instruction.push_str("mul"),
370 x if x == OpCode::Div as u8 => instruction.push_str("div"),
371 x if x == OpCode::Neg as u8 => instruction.push_str("neg"),
372 x if x == OpCode::Not as u8 => instruction.push_str("not"),
373 x if x == OpCode::Ceq as u8 => instruction.push_str("ceq"),
374 x if x == OpCode::Clt as u8 => instruction.push_str("clt"),
375 x if x == OpCode::Cgt as u8 => instruction.push_str("cgt"),
376 x if x == OpCode::Br as u8 => {
377 if let Some(target) = read_u32(code, &mut ip) {
378 instruction.push_str(&format!("br {target}"));
379 } else {
380 instruction.push_str("br <truncated>");
381 truncated = true;
382 }
383 }
384 x if x == OpCode::Brfalse as u8 => {
385 if let Some(target) = read_u32(code, &mut ip) {
386 instruction.push_str(&format!("brfalse {target}"));
387 } else {
388 instruction.push_str("brfalse <truncated>");
389 truncated = true;
390 }
391 }
392 x if x == OpCode::Pop as u8 => instruction.push_str("pop"),
393 x if x == OpCode::Dup as u8 => instruction.push_str("dup"),
394 x if x == OpCode::Ldloc as u8 => {
395 if let Some(index) = read_u8(code, &mut ip) {
396 instruction.push_str(&format!("ldloc {index}"));
397 } else {
398 instruction.push_str("ldloc <truncated>");
399 truncated = true;
400 }
401 }
402 x if x == OpCode::Stloc as u8 => {
403 if let Some(index) = read_u8(code, &mut ip) {
404 instruction.push_str(&format!("stloc {index}"));
405 } else {
406 instruction.push_str("stloc <truncated>");
407 truncated = true;
408 }
409 }
410 x if x == OpCode::Call as u8 => {
411 if let Some(index) = read_u16(code, &mut ip) {
412 if let Some(argc) = read_u8(code, &mut ip) {
413 instruction.push_str(&format!("call {index} {argc}"));
414 if let Some(comment) = format_call_target(program, index, argc) {
415 instruction.push_str(&format!(" ; {comment}"));
416 }
417 } else {
418 instruction.push_str("call <truncated>");
419 truncated = true;
420 }
421 } else {
422 instruction.push_str("call <truncated>");
423 truncated = true;
424 }
425 }
426 x if x == OpCode::Shl as u8 => instruction.push_str("shl"),
427 x if x == OpCode::Shr as u8 => instruction.push_str("shr"),
428 x if x == OpCode::Lshr as u8 => instruction.push_str("lshr"),
429 x if x == OpCode::Mod as u8 => instruction.push_str("mod"),
430 x if x == OpCode::And as u8 => instruction.push_str("and"),
431 x if x == OpCode::Or as u8 => instruction.push_str("or"),
432 other => instruction.push_str(&format!(".byte 0x{other:02X} ; invalid opcode")),
433 }
434
435 let encoded = format_hex_bytes(&code[start..ip]);
436 let _ = writeln!(&mut out, "{start:04}\t{encoded:<14}\t{instruction}");
437 if truncated {
438 break;
439 }
440 }
441
442 out
443}
444
445fn source_annotations(
446 program: &Program,
447 show_source: bool,
448) -> Option<BTreeMap<usize, Vec<(u32, String)>>> {
449 if !show_source {
450 return None;
451 }
452 let debug = program.debug.as_ref()?;
453 let source = debug.source.as_ref()?;
454 let source_lines = source.lines().collect::<Vec<_>>();
455 let mut first_offset_by_line = HashMap::<u32, u32>::new();
456 for info in &debug.lines {
457 first_offset_by_line.entry(info.line).or_insert(info.offset);
458 }
459 let mut pairs = first_offset_by_line
460 .into_iter()
461 .map(|(line, offset)| (offset, line))
462 .collect::<Vec<_>>();
463 pairs.sort_by_key(|(offset, line)| (*offset, *line));
464
465 let mut annotations = BTreeMap::<usize, Vec<(u32, String)>>::new();
466 for (offset, line) in pairs {
467 let text = source_lines
468 .get(line.saturating_sub(1) as usize)
469 .copied()
470 .unwrap_or("<missing source line>")
471 .to_string();
472 annotations
473 .entry(offset as usize)
474 .or_default()
475 .push((line, text));
476 }
477 Some(annotations)
478}
479
480struct ProgramAnalysis {
481 max_local_index: Option<u8>,
482}
483
484fn analyze_program(
485 program: &Program,
486 host_fn_count: Option<u16>,
487) -> Result<ProgramAnalysis, ValidationError> {
488 let mut ip = 0usize;
489 let mut instruction_starts = HashSet::new();
490 let mut jump_targets: Vec<(usize, u32)> = Vec::new();
491 let mut max_local_index: Option<u8> = None;
492 let code = &program.code;
493
494 while ip < code.len() {
495 let start = ip;
496 instruction_starts.insert(start);
497 let opcode = code[ip];
498 ip += 1;
499
500 match opcode {
501 x if x == OpCode::Nop as u8 || x == OpCode::Ret as u8 => {}
502 x if x == OpCode::Ldc as u8 => {
503 let index = read_u32(code, &mut ip).ok_or(ValidationError::TruncatedOperand {
504 offset: start,
505 opcode,
506 expected_bytes: 4,
507 })?;
508 if index as usize >= program.constants.len() {
509 return Err(ValidationError::InvalidConstant {
510 offset: start,
511 index,
512 });
513 }
514 }
515 x if x == OpCode::Add as u8
516 || x == OpCode::Sub as u8
517 || x == OpCode::Mul as u8
518 || x == OpCode::Div as u8
519 || x == OpCode::Shl as u8
520 || x == OpCode::Shr as u8
521 || x == OpCode::Lshr as u8
522 || x == OpCode::Mod as u8
523 || x == OpCode::And as u8
524 || x == OpCode::Or as u8
525 || x == OpCode::Neg as u8
526 || x == OpCode::Not as u8
527 || x == OpCode::Ceq as u8
528 || x == OpCode::Clt as u8
529 || x == OpCode::Cgt as u8
530 || x == OpCode::Pop as u8
531 || x == OpCode::Dup as u8 => {}
532 x if x == OpCode::Br as u8 || x == OpCode::Brfalse as u8 => {
533 let target = read_u32(code, &mut ip).ok_or(ValidationError::TruncatedOperand {
534 offset: start,
535 opcode,
536 expected_bytes: 4,
537 })?;
538 jump_targets.push((start, target));
539 }
540 x if x == OpCode::Ldloc as u8 || x == OpCode::Stloc as u8 => {
541 let index = read_u8(code, &mut ip).ok_or(ValidationError::TruncatedOperand {
542 offset: start,
543 opcode,
544 expected_bytes: 1,
545 })?;
546 max_local_index = Some(max_local_index.map_or(index, |prev| prev.max(index)));
547 }
548 x if x == OpCode::Call as u8 => {
549 let index = read_u16(code, &mut ip).ok_or(ValidationError::TruncatedOperand {
550 offset: start,
551 opcode,
552 expected_bytes: 3,
553 })?;
554 let argc = read_u8(code, &mut ip).ok_or(ValidationError::TruncatedOperand {
555 offset: start,
556 opcode,
557 expected_bytes: 3,
558 })?;
559 if let Some(builtin) = BuiltinFunction::from_call_index(index) {
560 if !builtin.accepts_arity(argc) {
561 return Err(ValidationError::InvalidCallArity {
562 offset: start,
563 index,
564 expected: builtin.arity(),
565 got: argc,
566 });
567 }
568 continue;
569 }
570 if program.imports.is_empty() {
571 if let Some(host_fn_count) = host_fn_count
572 && index >= host_fn_count
573 {
574 return Err(ValidationError::InvalidCall {
575 offset: start,
576 index,
577 });
578 }
579 } else {
580 let Some(import) = program.imports.get(index as usize) else {
581 return Err(ValidationError::InvalidCall {
582 offset: start,
583 index,
584 });
585 };
586 if argc != import.arity {
587 return Err(ValidationError::InvalidCallArity {
588 offset: start,
589 index,
590 expected: import.arity,
591 got: argc,
592 });
593 }
594 }
595 }
596 other => {
597 return Err(ValidationError::InvalidOpcode {
598 offset: start,
599 opcode: other,
600 });
601 }
602 }
603 }
604
605 for (offset, target) in &jump_targets {
606 let target = *target as usize;
607 if target >= code.len() || !instruction_starts.contains(&target) {
608 return Err(ValidationError::InvalidJumpTarget {
609 offset: *offset,
610 target: target as u32,
611 });
612 }
613 }
614
615 Ok(ProgramAnalysis { max_local_index })
616}
617
618fn write_debug_info(out: &mut Vec<u8>, debug: Option<&DebugInfo>) -> Result<(), WireError> {
619 match debug {
620 None => {
621 out.push(0);
622 Ok(())
623 }
624 Some(debug) => {
625 out.push(1);
626
627 match &debug.source {
628 None => out.push(0),
629 Some(source) => {
630 out.push(1);
631 write_string("debug source", source, out)?;
632 }
633 }
634
635 write_u32_count("debug lines", debug.lines.len(), out)?;
636 for line in &debug.lines {
637 out.extend_from_slice(&line.offset.to_le_bytes());
638 out.extend_from_slice(&line.line.to_le_bytes());
639 }
640
641 write_u32_count("debug functions", debug.functions.len(), out)?;
642 for function in &debug.functions {
643 write_string("debug function name", &function.name, out)?;
644 write_u32_count("debug function args", function.args.len(), out)?;
645 for arg in &function.args {
646 write_string("debug arg name", &arg.name, out)?;
647 out.push(arg.position);
648 }
649 }
650
651 write_u32_count("debug locals", debug.locals.len(), out)?;
652 for local in &debug.locals {
653 write_string("debug local name", &local.name, out)?;
654 out.push(local.index);
655 if ENCODE_VERSION >= VERSION_V5 {
656 write_optional_u32(local.declared_line, out);
657 write_optional_u32(local.last_line, out);
658 }
659 }
660
661 Ok(())
662 }
663 }
664}
665
666fn read_debug_info(cursor: &mut Cursor<'_>, version: u16) -> Result<Option<DebugInfo>, WireError> {
667 let flag = cursor.read_u8()?;
668 match flag {
669 0 => Ok(None),
670 1 => {
671 let source = match cursor.read_u8()? {
672 0 => None,
673 1 => Some(cursor.read_string()?),
674 other => return Err(WireError::InvalidDebugFlag(other)),
675 };
676
677 let line_count = cursor.read_u32()? as usize;
678 let mut lines = Vec::with_capacity(line_count);
679 for _ in 0..line_count {
680 lines.push(LineInfo {
681 offset: cursor.read_u32()?,
682 line: cursor.read_u32()?,
683 });
684 }
685
686 let function_count = cursor.read_u32()? as usize;
687 let mut functions = Vec::with_capacity(function_count);
688 for _ in 0..function_count {
689 let name = cursor.read_string()?;
690 let arg_count = cursor.read_u32()? as usize;
691 let mut args = Vec::with_capacity(arg_count);
692 for _ in 0..arg_count {
693 args.push(ArgInfo {
694 name: cursor.read_string()?,
695 position: cursor.read_u8()?,
696 });
697 }
698 functions.push(DebugFunction { name, args });
699 }
700
701 let locals = if version >= VERSION_V3 {
702 let local_count = cursor.read_u32()? as usize;
703 let mut locals = Vec::with_capacity(local_count);
704 for _ in 0..local_count {
705 let name = cursor.read_string()?;
706 let index = cursor.read_u8()?;
707 let (declared_line, last_line) = if version >= VERSION_V5 {
708 (read_optional_u32(cursor)?, read_optional_u32(cursor)?)
709 } else {
710 (None, None)
711 };
712 locals.push(LocalInfo {
713 name,
714 index,
715 declared_line,
716 last_line,
717 });
718 }
719 locals
720 } else {
721 Vec::new()
722 };
723
724 Ok(Some(DebugInfo {
725 source,
726 lines,
727 functions,
728 locals,
729 }))
730 }
731 other => Err(WireError::InvalidDebugFlag(other)),
732 }
733}
734
735fn write_type_map(out: &mut Vec<u8>, type_map: Option<&TypeMap>) -> Result<(), WireError> {
736 let Some(type_map) = type_map else {
737 out.push(0);
738 return Ok(());
739 };
740
741 out.push(1);
742 out.push(u8::from(type_map.strict_types));
743 write_u32_count("type map locals", type_map.local_types.len(), out)?;
744 for ty in &type_map.local_types {
745 out.push(*ty as u8);
746 }
747 for schema in &type_map.local_schemas {
748 write_optional_schema(schema.as_ref(), out)?;
749 }
750 write_bool_slice("type map callable slots", &type_map.callable_slots, out)?;
751 write_bool_slice("type map optional slots", &type_map.optional_slots, out)?;
752
753 write_u32_count("type map operands", type_map.operand_types.len(), out)?;
754 let mut operand_entries = type_map
755 .operand_types
756 .iter()
757 .map(|(offset, pair)| (*offset, *pair))
758 .collect::<Vec<_>>();
759 operand_entries.sort_unstable_by_key(|(offset, _)| *offset);
760 for (offset, (lhs, rhs)) in operand_entries {
761 write_u32_count("type map operand offset", offset, out)?;
762 out.push(lhs as u8);
763 out.push(rhs as u8);
764 }
765 Ok(())
766}
767
768fn read_type_map(cursor: &mut Cursor<'_>) -> Result<Option<TypeMap>, WireError> {
769 match cursor.read_u8()? {
770 0 => Ok(None),
771 1 => {
772 let strict_types = match cursor.read_u8()? {
773 0 => false,
774 1 => true,
775 other => return Err(WireError::InvalidBool(other)),
776 };
777 let local_count = cursor.read_u32()? as usize;
778 let mut local_types = Vec::with_capacity(local_count);
779 for _ in 0..local_count {
780 local_types.push(read_value_type(cursor.read_u8()?)?);
781 }
782 let mut local_schemas = Vec::with_capacity(local_count);
783 for _ in 0..local_count {
784 local_schemas.push(read_optional_schema(cursor)?);
785 }
786 let callable_slots = read_bool_vec(cursor, local_count)?;
787 let optional_slots = read_bool_vec(cursor, local_count)?;
788
789 let operand_count = cursor.read_u32()? as usize;
790 let mut operand_types = HashMap::with_capacity(operand_count);
791 for _ in 0..operand_count {
792 let offset = cursor.read_u32()? as usize;
793 let lhs = read_value_type(cursor.read_u8()?)?;
794 let rhs = read_value_type(cursor.read_u8()?)?;
795 operand_types.insert(offset, (lhs, rhs));
796 }
797
798 Ok(Some(TypeMap {
799 strict_types,
800 local_types,
801 local_schemas,
802 callable_slots,
803 optional_slots,
804 operand_types,
805 }))
806 }
807 other => Err(WireError::InvalidTypeMapFlag(other)),
808 }
809}
810
811fn read_value_type(raw: u8) -> Result<ValueType, WireError> {
812 match raw {
813 0 => Ok(ValueType::Unknown),
814 1 => Ok(ValueType::Null),
815 2 => Ok(ValueType::Int),
816 3 => Ok(ValueType::Float),
817 4 => Ok(ValueType::Bool),
818 5 => Ok(ValueType::String),
819 6 => Ok(ValueType::Bytes),
820 7 => Ok(ValueType::Array),
821 8 => Ok(ValueType::Map),
822 other => Err(WireError::InvalidValueType(other)),
823 }
824}
825
826fn write_optional_u32(value: Option<u32>, out: &mut Vec<u8>) {
827 match value {
828 Some(value) => {
829 out.push(1);
830 out.extend_from_slice(&value.to_le_bytes());
831 }
832 None => out.push(0),
833 }
834}
835
836fn read_optional_u32(cursor: &mut Cursor<'_>) -> Result<Option<u32>, WireError> {
837 match cursor.read_u8()? {
838 0 => Ok(None),
839 1 => Ok(Some(cursor.read_u32()?)),
840 other => Err(WireError::InvalidDebugFlag(other)),
841 }
842}
843
844fn write_bool_slice(
845 field: &'static str,
846 values: &[bool],
847 out: &mut Vec<u8>,
848) -> Result<(), WireError> {
849 write_u32_count(field, values.len(), out)?;
850 out.extend(values.iter().map(|value| u8::from(*value)));
851 Ok(())
852}
853
854fn read_bool_vec(cursor: &mut Cursor<'_>, expected_len: usize) -> Result<Vec<bool>, WireError> {
855 let count = cursor.read_u32()? as usize;
856 if count != expected_len {
857 return Err(WireError::TrailingBytes);
858 }
859 let mut values = Vec::with_capacity(count);
860 for _ in 0..count {
861 values.push(match cursor.read_u8()? {
862 0 => false,
863 1 => true,
864 other => return Err(WireError::InvalidBool(other)),
865 });
866 }
867 Ok(values)
868}
869
870fn write_optional_schema(schema: Option<&TypeSchema>, out: &mut Vec<u8>) -> Result<(), WireError> {
871 match schema {
872 Some(schema) => {
873 out.push(1);
874 write_schema(schema, out)?;
875 }
876 None => out.push(0),
877 }
878 Ok(())
879}
880
881fn read_optional_schema(cursor: &mut Cursor<'_>) -> Result<Option<TypeSchema>, WireError> {
882 match cursor.read_u8()? {
883 0 => Ok(None),
884 1 => Ok(Some(read_schema(cursor)?)),
885 other => Err(WireError::InvalidBool(other)),
886 }
887}
888
889fn write_schema(schema: &TypeSchema, out: &mut Vec<u8>) -> Result<(), WireError> {
890 match schema {
891 TypeSchema::Unknown => out.push(0),
892 TypeSchema::Null => out.push(1),
893 TypeSchema::Int => out.push(2),
894 TypeSchema::Float => out.push(3),
895 TypeSchema::Number => out.push(4),
896 TypeSchema::Bool => out.push(5),
897 TypeSchema::String => out.push(6),
898 TypeSchema::Bytes => out.push(7),
899 TypeSchema::Optional(inner) => {
900 out.push(16);
901 write_schema(inner, out)?;
902 }
903 TypeSchema::GenericParam(name) => {
904 out.push(8);
905 write_string("schema generic", name, out)?;
906 }
907 TypeSchema::Named(name, type_args) => {
908 out.push(9);
909 write_string("schema name", name, out)?;
910 write_u32_count("schema type args", type_args.len(), out)?;
911 for type_arg in type_args {
912 write_schema(type_arg, out)?;
913 }
914 }
915 TypeSchema::Array(item) => {
916 out.push(10);
917 write_schema(item, out)?;
918 }
919 TypeSchema::ArrayTuple(items) => {
920 out.push(11);
921 write_u32_count("schema tuple items", items.len(), out)?;
922 for item in items {
923 write_schema(item, out)?;
924 }
925 }
926 TypeSchema::ArrayTupleRest { prefix, rest } => {
927 out.push(12);
928 write_u32_count("schema tuple prefix", prefix.len(), out)?;
929 for item in prefix {
930 write_schema(item, out)?;
931 }
932 write_schema(rest, out)?;
933 }
934 TypeSchema::Map(item) => {
935 out.push(13);
936 write_schema(item, out)?;
937 }
938 TypeSchema::Object(fields) => {
939 out.push(14);
940 let mut entries = fields.iter().collect::<Vec<_>>();
941 entries.sort_unstable_by(|(lhs, _), (rhs, _)| lhs.cmp(rhs));
942 write_u32_count("schema object fields", entries.len(), out)?;
943 for (name, value) in entries {
944 write_string("schema object field", name, out)?;
945 write_schema(value, out)?;
946 }
947 }
948 TypeSchema::Callable { params, result } => {
949 out.push(15);
950 write_u32_count("schema callable params", params.len(), out)?;
951 for param in params {
952 write_schema(param, out)?;
953 }
954 write_schema(result, out)?;
955 }
956 }
957 Ok(())
958}
959
960fn read_schema(cursor: &mut Cursor<'_>) -> Result<TypeSchema, WireError> {
961 match cursor.read_u8()? {
962 0 => Ok(TypeSchema::Unknown),
963 1 => Ok(TypeSchema::Null),
964 2 => Ok(TypeSchema::Int),
965 3 => Ok(TypeSchema::Float),
966 4 => Ok(TypeSchema::Number),
967 5 => Ok(TypeSchema::Bool),
968 6 => Ok(TypeSchema::String),
969 7 => Ok(TypeSchema::Bytes),
970 16 => Ok(TypeSchema::Optional(Box::new(read_schema(cursor)?))),
971 8 => Ok(TypeSchema::GenericParam(cursor.read_string()?)),
972 9 => {
973 let name = cursor.read_string()?;
974 let count = cursor.read_u32()? as usize;
975 let mut type_args = Vec::with_capacity(count);
976 for _ in 0..count {
977 type_args.push(read_schema(cursor)?);
978 }
979 Ok(TypeSchema::Named(name, type_args))
980 }
981 10 => Ok(TypeSchema::Array(Box::new(read_schema(cursor)?))),
982 11 => {
983 let count = cursor.read_u32()? as usize;
984 let mut items = Vec::with_capacity(count);
985 for _ in 0..count {
986 items.push(read_schema(cursor)?);
987 }
988 Ok(TypeSchema::ArrayTuple(items))
989 }
990 12 => {
991 let count = cursor.read_u32()? as usize;
992 let mut prefix = Vec::with_capacity(count);
993 for _ in 0..count {
994 prefix.push(read_schema(cursor)?);
995 }
996 let rest = Box::new(read_schema(cursor)?);
997 Ok(TypeSchema::ArrayTupleRest { prefix, rest })
998 }
999 13 => Ok(TypeSchema::Map(Box::new(read_schema(cursor)?))),
1000 14 => {
1001 let count = cursor.read_u32()? as usize;
1002 let mut fields = HashMap::with_capacity(count);
1003 for _ in 0..count {
1004 let name = cursor.read_string()?;
1005 let value = read_schema(cursor)?;
1006 fields.insert(name, value);
1007 }
1008 Ok(TypeSchema::Object(fields))
1009 }
1010 15 => {
1011 let count = cursor.read_u32()? as usize;
1012 let mut params = Vec::with_capacity(count);
1013 for _ in 0..count {
1014 params.push(read_schema(cursor)?);
1015 }
1016 let result = Box::new(read_schema(cursor)?);
1017 Ok(TypeSchema::Callable { params, result })
1018 }
1019 other => Err(WireError::InvalidValueType(other)),
1020 }
1021}
1022
1023fn write_string(field: &'static str, value: &str, out: &mut Vec<u8>) -> Result<(), WireError> {
1024 write_u32_len(field, value.len(), out)?;
1025 out.extend_from_slice(value.as_bytes());
1026 Ok(())
1027}
1028
1029fn write_u32_len(field: &'static str, len: usize, out: &mut Vec<u8>) -> Result<(), WireError> {
1030 let len_u32 = u32::try_from(len).map_err(|_| WireError::LengthTooLarge(field, len))?;
1031 out.extend_from_slice(&len_u32.to_le_bytes());
1032 Ok(())
1033}
1034
1035fn write_u32_count(field: &'static str, count: usize, out: &mut Vec<u8>) -> Result<(), WireError> {
1036 write_u32_len(field, count, out)
1037}
1038
1039struct Cursor<'a> {
1040 bytes: &'a [u8],
1041 offset: usize,
1042}
1043
1044impl<'a> Cursor<'a> {
1045 fn new(bytes: &'a [u8]) -> Self {
1046 Self { bytes, offset: 0 }
1047 }
1048
1049 fn read_u8(&mut self) -> Result<u8, WireError> {
1050 let value = self
1051 .bytes
1052 .get(self.offset)
1053 .ok_or(WireError::UnexpectedEof)?;
1054 self.offset += 1;
1055 Ok(*value)
1056 }
1057
1058 fn read_u16(&mut self) -> Result<u16, WireError> {
1059 let bytes = self.read_exact_array::<2>()?;
1060 Ok(u16::from_le_bytes(bytes))
1061 }
1062
1063 fn read_u32(&mut self) -> Result<u32, WireError> {
1064 let bytes = self.read_exact_array::<4>()?;
1065 Ok(u32::from_le_bytes(bytes))
1066 }
1067
1068 fn read_i64(&mut self) -> Result<i64, WireError> {
1069 let bytes = self.read_exact_array::<8>()?;
1070 Ok(i64::from_le_bytes(bytes))
1071 }
1072
1073 fn read_f64(&mut self) -> Result<f64, WireError> {
1074 let bytes = self.read_exact_array::<8>()?;
1075 Ok(f64::from_le_bytes(bytes))
1076 }
1077
1078 fn read_string(&mut self) -> Result<String, WireError> {
1079 let len = self.read_u32()? as usize;
1080 let bytes = self.read_exact(len)?;
1081 String::from_utf8(bytes.to_vec()).map_err(|_| WireError::InvalidUtf8)
1082 }
1083
1084 fn read_exact_array<const N: usize>(&mut self) -> Result<[u8; N], WireError> {
1085 let bytes = self.read_exact(N)?;
1086 let mut out = [0u8; N];
1087 out.copy_from_slice(bytes);
1088 Ok(out)
1089 }
1090
1091 fn read_exact(&mut self, len: usize) -> Result<&'a [u8], WireError> {
1092 let end = self
1093 .offset
1094 .checked_add(len)
1095 .ok_or(WireError::UnexpectedEof)?;
1096 if end > self.bytes.len() {
1097 return Err(WireError::UnexpectedEof);
1098 }
1099 let slice = &self.bytes[self.offset..end];
1100 self.offset = end;
1101 Ok(slice)
1102 }
1103
1104 fn is_eof(&self) -> bool {
1105 self.offset == self.bytes.len()
1106 }
1107}
1108
1109fn read_u8(code: &[u8], ip: &mut usize) -> Option<u8> {
1110 let value = *code.get(*ip)?;
1111 *ip += 1;
1112 Some(value)
1113}
1114
1115fn read_u16(code: &[u8], ip: &mut usize) -> Option<u16> {
1116 let bytes = code.get(*ip..(*ip + 2))?;
1117 *ip += 2;
1118 Some(u16::from_le_bytes([bytes[0], bytes[1]]))
1119}
1120
1121fn read_u32(code: &[u8], ip: &mut usize) -> Option<u32> {
1122 let bytes = code.get(*ip..(*ip + 4))?;
1123 *ip += 4;
1124 Some(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
1125}
1126
1127fn format_hex_bytes(bytes: &[u8]) -> String {
1128 let mut out = String::new();
1129 for (idx, byte) in bytes.iter().enumerate() {
1130 if idx > 0 {
1131 out.push(' ');
1132 }
1133 out.push_str(&format!("{byte:02X}"));
1134 }
1135 out
1136}
1137
1138fn format_call_target(program: &Program, index: u16, argc: u8) -> Option<String> {
1139 if let Some(builtin) = BuiltinFunction::from_call_index(index) {
1140 return Some(format!("builtin {}/{}", builtin.name(), builtin.arity()));
1141 }
1142 program
1143 .imports
1144 .get(index as usize)
1145 .map(|import| format!("import {}/{} (argc={argc})", import.name, import.arity))
1146}