1use std::io;
2use std::path::Path;
3
4use crate::bytecode::{Program, TypeMap, Value, ValueType};
5use crate::compiler::ir::TypeSchema;
6use crate::vm::native::{
7 helper_entry_offset, interrupt_helper_entry_offset, selected_codegen_backend,
8};
9use crate::vm::{Vm, VmError};
10
11use super::super::jit::JitConfig;
12use super::compile::CompiledProgram;
13
14const MAGIC: [u8; 4] = *b"PAT\0";
15const VERSION: u16 = 2;
16const ABI_VERSION: u16 = 1;
17const FLAGS: u16 = 0;
18
19#[derive(Debug)]
20pub enum AotArtifactError {
21 Io(io::Error),
22 Vm(VmError),
23 Wire(crate::WireError),
24 MissingAotProgram,
25 UnexpectedEof,
26 InvalidMagic([u8; 4]),
27 UnsupportedVersion(u16),
28 UnsupportedAbiVersion(u16),
29 UnsupportedFlags(u16),
30 InvalidUtf8,
31 TrailingBytes,
32 LengthTooLarge(&'static str, usize),
33 IncompatibleProgramHash {
34 expected: u64,
35 found: u64,
36 },
37 EmbeddedProgramHashMismatch {
38 stored: u64,
39 computed: u64,
40 },
41 InvalidValueTag(u8),
42 InvalidBool(u8),
43 InvalidValueType(u8),
44 IncompatibleRuntime {
45 field: &'static str,
46 expected: String,
47 found: String,
48 },
49}
50
51impl std::fmt::Display for AotArtifactError {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 Self::Io(err) => write!(f, "{err}"),
55 Self::Vm(err) => write!(f, "{err}"),
56 Self::Wire(err) => write!(f, "{err}"),
57 Self::MissingAotProgram => write!(f, "whole-program aot is not compiled"),
58 Self::UnexpectedEof => write!(f, "unexpected end of aot artifact"),
59 Self::InvalidMagic(found) => write!(f, "invalid aot artifact magic: {found:?}"),
60 Self::UnsupportedVersion(version) => {
61 write!(f, "unsupported aot artifact version: {version}")
62 }
63 Self::UnsupportedAbiVersion(version) => {
64 write!(f, "unsupported aot artifact abi version: {version}")
65 }
66 Self::UnsupportedFlags(flags) => write!(f, "unsupported aot artifact flags: {flags}"),
67 Self::InvalidUtf8 => write!(f, "invalid utf-8 in aot artifact"),
68 Self::TrailingBytes => write!(f, "trailing bytes after aot artifact payload"),
69 Self::LengthTooLarge(field, len) => write!(f, "{field} length too large: {len}"),
70 Self::IncompatibleProgramHash { expected, found } => write!(
71 f,
72 "aot artifact program hash mismatch: expected {expected}, found {found}"
73 ),
74 Self::EmbeddedProgramHashMismatch { stored, computed } => write!(
75 f,
76 "aot artifact embedded program hash mismatch: stored {stored}, computed {computed}"
77 ),
78 Self::InvalidValueTag(tag) => write!(f, "invalid aot artifact value tag: {tag}"),
79 Self::InvalidBool(value) => write!(f, "invalid bool value in aot artifact: {value}"),
80 Self::InvalidValueType(value) => {
81 write!(f, "invalid value type in aot artifact: {value}")
82 }
83 Self::IncompatibleRuntime {
84 field,
85 expected,
86 found,
87 } => write!(
88 f,
89 "aot artifact runtime mismatch for {field}: expected {expected}, found {found}"
90 ),
91 }
92 }
93}
94
95impl std::error::Error for AotArtifactError {}
96
97impl From<VmError> for AotArtifactError {
98 fn from(value: VmError) -> Self {
99 Self::Vm(value)
100 }
101}
102
103impl From<crate::WireError> for AotArtifactError {
104 fn from(value: crate::WireError) -> Self {
105 Self::Wire(value)
106 }
107}
108
109impl Vm {
110 pub fn encode_aot_artifact(&mut self) -> Result<Vec<u8>, AotArtifactError> {
111 if self.aot_program.is_none() {
112 self.compile_aot()?;
113 }
114 let program_hash = self.ensure_program_cache_key();
115 let aot_program = self
116 .aot_program
117 .as_ref()
118 .ok_or(AotArtifactError::MissingAotProgram)?;
119 encode_artifact(self.program(), aot_program, program_hash)
120 }
121
122 pub fn save_aot_artifact_to_file<P: AsRef<Path>>(
123 &mut self,
124 path: P,
125 ) -> Result<(), AotArtifactError> {
126 let bytes = self.encode_aot_artifact()?;
127 std::fs::write(path, bytes).map_err(AotArtifactError::Io)
128 }
129
130 pub fn load_aot_artifact(&mut self, bytes: &[u8]) -> Result<(), AotArtifactError> {
131 let expected_program_hash = self.ensure_program_cache_key();
132 let decoded = decode_artifact(bytes, Some(expected_program_hash))?;
133 self.aot_program = Some(CompiledProgram::from_code(
134 decoded.code,
135 decoded.resume_ips,
136 )?);
137 self.aot_exec_count = 0;
138 Ok(())
139 }
140
141 pub fn load_aot_artifact_from_file<P: AsRef<Path>>(
142 &mut self,
143 path: P,
144 ) -> Result<(), AotArtifactError> {
145 let bytes = std::fs::read(path).map_err(AotArtifactError::Io)?;
146 self.load_aot_artifact(&bytes)
147 }
148
149 pub fn new_from_aot_artifact_with_jit_config(
150 bytes: &[u8],
151 jit_config: JitConfig,
152 ) -> Result<Self, AotArtifactError> {
153 let decoded = decode_artifact(bytes, None)?;
154 let mut vm = Vm::new_with_jit_config(decoded.program, jit_config);
155 vm.aot_program = Some(CompiledProgram::from_code(
156 decoded.code,
157 decoded.resume_ips,
158 )?);
159 vm.aot_exec_count = 0;
160 Ok(vm)
161 }
162
163 pub fn new_from_aot_artifact_file_with_jit_config<P: AsRef<Path>>(
164 path: P,
165 jit_config: JitConfig,
166 ) -> Result<Self, AotArtifactError> {
167 let bytes = std::fs::read(path).map_err(AotArtifactError::Io)?;
168 Self::new_from_aot_artifact_with_jit_config(&bytes, jit_config)
169 }
170}
171
172struct DecodedArtifact {
173 program: Program,
174 code: Vec<u8>,
175 resume_ips: Vec<usize>,
176}
177
178fn encode_artifact(
179 source_program: &Program,
180 aot_program: &CompiledProgram,
181 program_hash: u64,
182) -> Result<Vec<u8>, AotArtifactError> {
183 let mut out = Vec::new();
184 out.extend_from_slice(&MAGIC);
185 out.extend_from_slice(&VERSION.to_le_bytes());
186 out.extend_from_slice(&ABI_VERSION.to_le_bytes());
187 out.extend_from_slice(&FLAGS.to_le_bytes());
188 out.push(u8::try_from(std::mem::size_of::<usize>()).expect("pointer width fits u8"));
189
190 write_string("arch", std::env::consts::ARCH, &mut out)?;
191 write_string("os", std::env::consts::OS, &mut out)?;
192 write_string("backend", selected_codegen_backend(), &mut out)?;
193
194 write_u32("vm ip offset", std::mem::offset_of!(Vm, ip), &mut out)?;
195 write_u32(
196 "native helper offset",
197 helper_entry_offset() as usize,
198 &mut out,
199 )?;
200 write_u32(
201 "interrupt helper offset",
202 interrupt_helper_entry_offset() as usize,
203 &mut out,
204 )?;
205 out.extend_from_slice(&program_hash.to_le_bytes());
206
207 write_u32("program local count", source_program.local_count, &mut out)?;
208 let encoded_program = encode_program_payload(source_program)?;
209 write_u32("embedded program length", encoded_program.len(), &mut out)?;
210 out.extend_from_slice(&encoded_program);
211
212 write_u32("resume ip count", aot_program.resume_ips.len(), &mut out)?;
213 for &ip in aot_program.resume_ips.iter() {
214 write_u32("resume ip", ip, &mut out)?;
215 }
216
217 write_u32(
218 "native code length",
219 aot_program.code_bytes().len(),
220 &mut out,
221 )?;
222 out.extend_from_slice(aot_program.code_bytes());
223 Ok(out)
224}
225
226fn decode_artifact(
227 bytes: &[u8],
228 expected_program_hash: Option<u64>,
229) -> Result<DecodedArtifact, AotArtifactError> {
230 let mut cursor = Cursor::new(bytes);
231
232 let magic = cursor.read_exact_array::<4>()?;
233 if magic != MAGIC {
234 return Err(AotArtifactError::InvalidMagic(magic));
235 }
236
237 let version = cursor.read_u16()?;
238 if version != VERSION {
239 return Err(AotArtifactError::UnsupportedVersion(version));
240 }
241
242 let abi_version = cursor.read_u16()?;
243 if abi_version != ABI_VERSION {
244 return Err(AotArtifactError::UnsupportedAbiVersion(abi_version));
245 }
246
247 let flags = cursor.read_u16()?;
248 if flags != FLAGS {
249 return Err(AotArtifactError::UnsupportedFlags(flags));
250 }
251
252 let pointer_width = cursor.read_u8()?;
253 validate_runtime_field(
254 "pointer width",
255 std::mem::size_of::<usize>().to_string(),
256 pointer_width.to_string(),
257 )?;
258 validate_runtime_field(
259 "arch",
260 std::env::consts::ARCH.to_string(),
261 cursor.read_string()?,
262 )?;
263 validate_runtime_field(
264 "os",
265 std::env::consts::OS.to_string(),
266 cursor.read_string()?,
267 )?;
268 validate_runtime_field(
269 "backend",
270 selected_codegen_backend().to_string(),
271 cursor.read_string()?,
272 )?;
273 validate_runtime_field(
274 "vm ip offset",
275 std::mem::offset_of!(Vm, ip).to_string(),
276 cursor.read_u32()?.to_string(),
277 )?;
278 validate_runtime_field(
279 "native helper offset",
280 helper_entry_offset().to_string(),
281 cursor.read_u32()?.to_string(),
282 )?;
283 validate_runtime_field(
284 "interrupt helper offset",
285 interrupt_helper_entry_offset().to_string(),
286 cursor.read_u32()?.to_string(),
287 )?;
288
289 let found_program_hash = cursor.read_u64()?;
290 let local_count = cursor.read_u32()? as usize;
291 let embedded_program_len = cursor.read_u32()? as usize;
292 let mut program = decode_program_payload(cursor.read_exact(embedded_program_len)?)?;
293 program.local_count = local_count;
294 let computed_hash = super::super::compute_program_cache_key(&program);
295 if computed_hash != found_program_hash {
296 return Err(AotArtifactError::EmbeddedProgramHashMismatch {
297 stored: found_program_hash,
298 computed: computed_hash,
299 });
300 }
301
302 if let Some(expected_program_hash) = expected_program_hash
303 && found_program_hash != expected_program_hash
304 {
305 return Err(AotArtifactError::IncompatibleProgramHash {
306 expected: expected_program_hash,
307 found: found_program_hash,
308 });
309 }
310
311 let resume_count = cursor.read_u32()? as usize;
312 let mut resume_ips = Vec::with_capacity(resume_count);
313 for _ in 0..resume_count {
314 resume_ips.push(cursor.read_u32()? as usize);
315 }
316
317 let code_len = cursor.read_u32()? as usize;
318 let code = cursor.read_exact(code_len)?.to_vec();
319
320 if !cursor.is_at_end() {
321 return Err(AotArtifactError::TrailingBytes);
322 }
323
324 Ok(DecodedArtifact {
325 program,
326 code,
327 resume_ips,
328 })
329}
330
331fn validate_runtime_field(
332 field: &'static str,
333 expected: String,
334 found: String,
335) -> Result<(), AotArtifactError> {
336 if expected == found {
337 return Ok(());
338 }
339 Err(AotArtifactError::IncompatibleRuntime {
340 field,
341 expected,
342 found,
343 })
344}
345
346fn write_u32(field: &'static str, value: usize, out: &mut Vec<u8>) -> Result<(), AotArtifactError> {
347 let value = u32::try_from(value).map_err(|_| AotArtifactError::LengthTooLarge(field, value))?;
348 out.extend_from_slice(&value.to_le_bytes());
349 Ok(())
350}
351
352fn write_string(
353 field: &'static str,
354 value: &str,
355 out: &mut Vec<u8>,
356) -> Result<(), AotArtifactError> {
357 write_u32(field, value.len(), out)?;
358 out.extend_from_slice(value.as_bytes());
359 Ok(())
360}
361
362fn encode_program_payload(program: &Program) -> Result<Vec<u8>, AotArtifactError> {
363 let mut out = Vec::new();
364 write_u32("program constants", program.constants.len(), &mut out)?;
365 for constant in &program.constants {
366 write_value(constant, &mut out)?;
367 }
368 write_u32("program code length", program.code.len(), &mut out)?;
369 out.extend_from_slice(&program.code);
370 write_u32("program imports", program.imports.len(), &mut out)?;
371 for import in &program.imports {
372 write_string("import name", &import.name, &mut out)?;
373 out.push(import.arity);
374 out.push(import.return_type as u8);
375 }
376 write_type_map(program.type_map.as_ref(), &mut out)?;
377 Ok(out)
378}
379
380fn decode_program_payload(bytes: &[u8]) -> Result<Program, AotArtifactError> {
381 let mut cursor = Cursor::new(bytes);
382
383 let constant_count = cursor.read_u32()? as usize;
384 let mut constants = Vec::with_capacity(constant_count);
385 for _ in 0..constant_count {
386 constants.push(cursor.read_value()?);
387 }
388
389 let code_len = cursor.read_u32()? as usize;
390 let code = cursor.read_exact(code_len)?.to_vec();
391
392 let import_count = cursor.read_u32()? as usize;
393 let mut imports = Vec::with_capacity(import_count);
394 for _ in 0..import_count {
395 imports.push(crate::bytecode::HostImport {
396 name: cursor.read_string()?,
397 arity: cursor.read_u8()?,
398 return_type: read_value_type(cursor.read_u8()?)?,
399 });
400 }
401
402 let type_map = read_type_map(&mut cursor)?;
403 if !cursor.is_at_end() {
404 return Err(AotArtifactError::TrailingBytes);
405 }
406
407 let mut program = Program::with_imports_and_debug(constants, code, imports, None);
408 program.type_map = type_map;
409 Ok(program)
410}
411
412fn write_value(value: &Value, out: &mut Vec<u8>) -> Result<(), AotArtifactError> {
413 match value {
414 Value::Null => out.push(0),
415 Value::Int(value) => {
416 out.push(1);
417 out.extend_from_slice(&value.to_le_bytes());
418 }
419 Value::Float(value) => {
420 out.push(2);
421 out.extend_from_slice(&value.to_le_bytes());
422 }
423 Value::Bool(value) => {
424 out.push(3);
425 out.push(u8::from(*value));
426 }
427 Value::String(value) => {
428 out.push(4);
429 write_u32("value string", value.len(), out)?;
430 out.extend_from_slice(value.as_bytes());
431 }
432 Value::Bytes(value) => {
433 out.push(5);
434 write_u32("value bytes", value.len(), out)?;
435 out.extend_from_slice(value.as_slice());
436 }
437 Value::Array(values) => {
438 out.push(6);
439 write_u32("value array", values.len(), out)?;
440 for value in values.iter() {
441 write_value(value, out)?;
442 }
443 }
444 Value::Map(entries) => {
445 out.push(7);
446 let mut encoded_entries = entries
447 .iter()
448 .map(|(key, value)| {
449 let mut entry_bytes = Vec::new();
450 write_value(key, &mut entry_bytes)?;
451 write_value(value, &mut entry_bytes)?;
452 Ok(entry_bytes)
453 })
454 .collect::<Result<Vec<_>, AotArtifactError>>()?;
455 encoded_entries.sort_unstable();
456 write_u32("value map", encoded_entries.len(), out)?;
457 for entry in encoded_entries {
458 out.extend_from_slice(&entry);
459 }
460 }
461 }
462 Ok(())
463}
464
465fn write_type_map(type_map: Option<&TypeMap>, out: &mut Vec<u8>) -> Result<(), AotArtifactError> {
466 let Some(type_map) = type_map else {
467 out.push(0);
468 return Ok(());
469 };
470
471 out.push(1);
472 out.push(u8::from(type_map.strict_types));
473 write_u32("type map locals", type_map.local_types.len(), out)?;
474 for ty in &type_map.local_types {
475 out.push(*ty as u8);
476 }
477 for schema in &type_map.local_schemas {
478 write_optional_schema(schema.as_ref(), out)?;
479 }
480 write_bool_slice("type map callable slots", &type_map.callable_slots, out)?;
481 write_bool_slice("type map optional slots", &type_map.optional_slots, out)?;
482
483 let mut operand_entries = type_map
484 .operand_types
485 .iter()
486 .map(|(offset, pair)| (*offset, *pair))
487 .collect::<Vec<_>>();
488 operand_entries.sort_unstable_by_key(|(offset, _)| *offset);
489 write_u32("type map operands", operand_entries.len(), out)?;
490 for (offset, (lhs, rhs)) in operand_entries {
491 write_u32("type map operand offset", offset, out)?;
492 out.push(lhs as u8);
493 out.push(rhs as u8);
494 }
495 Ok(())
496}
497
498fn read_type_map(cursor: &mut Cursor<'_>) -> Result<Option<TypeMap>, AotArtifactError> {
499 let flag = cursor.read_u8()?;
500 if flag == 0 {
501 return Ok(None);
502 }
503 if flag != 1 {
504 return Err(AotArtifactError::InvalidValueType(flag));
505 }
506
507 let strict_types = match cursor.read_u8()? {
508 0 => false,
509 1 => true,
510 other => return Err(AotArtifactError::InvalidValueType(other)),
511 };
512 let local_count = cursor.read_u32()? as usize;
513 let mut local_types = Vec::with_capacity(local_count);
514 for _ in 0..local_count {
515 local_types.push(read_value_type(cursor.read_u8()?)?);
516 }
517 let mut local_schemas = Vec::with_capacity(local_count);
518 for _ in 0..local_count {
519 local_schemas.push(read_optional_schema(cursor)?);
520 }
521 let callable_slots = read_bool_vec(cursor, local_count)?;
522 let optional_slots = read_bool_vec(cursor, local_count)?;
523
524 let operand_count = cursor.read_u32()? as usize;
525 let mut operand_types = std::collections::HashMap::with_capacity(operand_count);
526 for _ in 0..operand_count {
527 let offset = cursor.read_u32()? as usize;
528 let lhs = read_value_type(cursor.read_u8()?)?;
529 let rhs = read_value_type(cursor.read_u8()?)?;
530 operand_types.insert(offset, (lhs, rhs));
531 }
532
533 Ok(Some(TypeMap {
534 strict_types,
535 local_types,
536 local_schemas,
537 callable_slots,
538 optional_slots,
539 operand_types,
540 }))
541}
542
543fn read_value_type(raw: u8) -> Result<ValueType, AotArtifactError> {
544 match raw {
545 0 => Ok(ValueType::Unknown),
546 1 => Ok(ValueType::Null),
547 2 => Ok(ValueType::Int),
548 3 => Ok(ValueType::Float),
549 4 => Ok(ValueType::Bool),
550 5 => Ok(ValueType::String),
551 6 => Ok(ValueType::Bytes),
552 7 => Ok(ValueType::Array),
553 8 => Ok(ValueType::Map),
554 other => Err(AotArtifactError::InvalidValueType(other)),
555 }
556}
557
558fn write_bool_slice(
559 field: &'static str,
560 values: &[bool],
561 out: &mut Vec<u8>,
562) -> Result<(), AotArtifactError> {
563 write_u32(field, values.len(), out)?;
564 out.extend(values.iter().map(|value| u8::from(*value)));
565 Ok(())
566}
567
568fn read_bool_vec(
569 cursor: &mut Cursor<'_>,
570 expected_len: usize,
571) -> Result<Vec<bool>, AotArtifactError> {
572 let count = cursor.read_u32()? as usize;
573 if count != expected_len {
574 return Err(AotArtifactError::UnexpectedEof);
575 }
576 let mut values = Vec::with_capacity(count);
577 for _ in 0..count {
578 values.push(match cursor.read_u8()? {
579 0 => false,
580 1 => true,
581 other => return Err(AotArtifactError::InvalidValueType(other)),
582 });
583 }
584 Ok(values)
585}
586
587fn write_optional_schema(
588 schema: Option<&TypeSchema>,
589 out: &mut Vec<u8>,
590) -> Result<(), AotArtifactError> {
591 match schema {
592 Some(schema) => {
593 out.push(1);
594 write_schema(schema, out)?;
595 }
596 None => out.push(0),
597 }
598 Ok(())
599}
600
601fn read_optional_schema(cursor: &mut Cursor<'_>) -> Result<Option<TypeSchema>, AotArtifactError> {
602 match cursor.read_u8()? {
603 0 => Ok(None),
604 1 => Ok(Some(read_schema(cursor)?)),
605 other => Err(AotArtifactError::InvalidValueType(other)),
606 }
607}
608
609fn write_schema(schema: &TypeSchema, out: &mut Vec<u8>) -> Result<(), AotArtifactError> {
610 match schema {
611 TypeSchema::Unknown => out.push(0),
612 TypeSchema::Null => out.push(1),
613 TypeSchema::Int => out.push(2),
614 TypeSchema::Float => out.push(3),
615 TypeSchema::Number => out.push(4),
616 TypeSchema::Bool => out.push(5),
617 TypeSchema::String => out.push(6),
618 TypeSchema::Bytes => out.push(7),
619 TypeSchema::Optional(inner) => {
620 out.push(16);
621 write_schema(inner, out)?;
622 }
623 TypeSchema::GenericParam(name) => {
624 out.push(8);
625 write_string("schema generic", name, out)?;
626 }
627 TypeSchema::Named(name, type_args) => {
628 out.push(9);
629 write_string("schema name", name, out)?;
630 write_u32("schema type args", type_args.len(), out)?;
631 for type_arg in type_args {
632 write_schema(type_arg, out)?;
633 }
634 }
635 TypeSchema::Array(item) => {
636 out.push(10);
637 write_schema(item, out)?;
638 }
639 TypeSchema::ArrayTuple(items) => {
640 out.push(11);
641 write_u32("schema tuple items", items.len(), out)?;
642 for item in items {
643 write_schema(item, out)?;
644 }
645 }
646 TypeSchema::ArrayTupleRest { prefix, rest } => {
647 out.push(12);
648 write_u32("schema tuple prefix", prefix.len(), out)?;
649 for item in prefix {
650 write_schema(item, out)?;
651 }
652 write_schema(rest, out)?;
653 }
654 TypeSchema::Map(item) => {
655 out.push(13);
656 write_schema(item, out)?;
657 }
658 TypeSchema::Object(fields) => {
659 out.push(14);
660 let mut entries = fields.iter().collect::<Vec<_>>();
661 entries.sort_unstable_by(|(lhs, _), (rhs, _)| lhs.cmp(rhs));
662 write_u32("schema object fields", entries.len(), out)?;
663 for (name, value) in entries {
664 write_string("schema object field", name, out)?;
665 write_schema(value, out)?;
666 }
667 }
668 TypeSchema::Callable { params, result } => {
669 out.push(15);
670 write_u32("schema callable params", params.len(), out)?;
671 for param in params {
672 write_schema(param, out)?;
673 }
674 write_schema(result, out)?;
675 }
676 }
677 Ok(())
678}
679
680fn read_schema(cursor: &mut Cursor<'_>) -> Result<TypeSchema, AotArtifactError> {
681 match cursor.read_u8()? {
682 0 => Ok(TypeSchema::Unknown),
683 1 => Ok(TypeSchema::Null),
684 2 => Ok(TypeSchema::Int),
685 3 => Ok(TypeSchema::Float),
686 4 => Ok(TypeSchema::Number),
687 5 => Ok(TypeSchema::Bool),
688 6 => Ok(TypeSchema::String),
689 7 => Ok(TypeSchema::Bytes),
690 16 => Ok(TypeSchema::Optional(Box::new(read_schema(cursor)?))),
691 8 => Ok(TypeSchema::GenericParam(cursor.read_string()?)),
692 9 => {
693 let name = cursor.read_string()?;
694 let count = cursor.read_u32()? as usize;
695 let mut type_args = Vec::with_capacity(count);
696 for _ in 0..count {
697 type_args.push(read_schema(cursor)?);
698 }
699 Ok(TypeSchema::Named(name, type_args))
700 }
701 10 => Ok(TypeSchema::Array(Box::new(read_schema(cursor)?))),
702 11 => {
703 let count = cursor.read_u32()? as usize;
704 let mut items = Vec::with_capacity(count);
705 for _ in 0..count {
706 items.push(read_schema(cursor)?);
707 }
708 Ok(TypeSchema::ArrayTuple(items))
709 }
710 12 => {
711 let count = cursor.read_u32()? as usize;
712 let mut prefix = Vec::with_capacity(count);
713 for _ in 0..count {
714 prefix.push(read_schema(cursor)?);
715 }
716 let rest = Box::new(read_schema(cursor)?);
717 Ok(TypeSchema::ArrayTupleRest { prefix, rest })
718 }
719 13 => Ok(TypeSchema::Map(Box::new(read_schema(cursor)?))),
720 14 => {
721 let count = cursor.read_u32()? as usize;
722 let mut fields = std::collections::HashMap::with_capacity(count);
723 for _ in 0..count {
724 let name = cursor.read_string()?;
725 let value = read_schema(cursor)?;
726 fields.insert(name, value);
727 }
728 Ok(TypeSchema::Object(fields))
729 }
730 15 => {
731 let count = cursor.read_u32()? as usize;
732 let mut params = Vec::with_capacity(count);
733 for _ in 0..count {
734 params.push(read_schema(cursor)?);
735 }
736 let result = Box::new(read_schema(cursor)?);
737 Ok(TypeSchema::Callable { params, result })
738 }
739 other => Err(AotArtifactError::InvalidValueType(other)),
740 }
741}
742
743struct Cursor<'a> {
744 bytes: &'a [u8],
745 offset: usize,
746}
747
748impl<'a> Cursor<'a> {
749 fn new(bytes: &'a [u8]) -> Self {
750 Self { bytes, offset: 0 }
751 }
752
753 fn read_exact(&mut self, len: usize) -> Result<&'a [u8], AotArtifactError> {
754 let end = self
755 .offset
756 .checked_add(len)
757 .ok_or(AotArtifactError::UnexpectedEof)?;
758 let slice = self
759 .bytes
760 .get(self.offset..end)
761 .ok_or(AotArtifactError::UnexpectedEof)?;
762 self.offset = end;
763 Ok(slice)
764 }
765
766 fn read_exact_array<const N: usize>(&mut self) -> Result<[u8; N], AotArtifactError> {
767 let bytes = self.read_exact(N)?;
768 let mut out = [0u8; N];
769 out.copy_from_slice(bytes);
770 Ok(out)
771 }
772
773 fn read_u8(&mut self) -> Result<u8, AotArtifactError> {
774 Ok(self.read_exact(1)?[0])
775 }
776
777 fn read_u16(&mut self) -> Result<u16, AotArtifactError> {
778 Ok(u16::from_le_bytes(self.read_exact_array::<2>()?))
779 }
780
781 fn read_u32(&mut self) -> Result<u32, AotArtifactError> {
782 Ok(u32::from_le_bytes(self.read_exact_array::<4>()?))
783 }
784
785 fn read_u64(&mut self) -> Result<u64, AotArtifactError> {
786 Ok(u64::from_le_bytes(self.read_exact_array::<8>()?))
787 }
788
789 fn read_string(&mut self) -> Result<String, AotArtifactError> {
790 let len = self.read_u32()? as usize;
791 String::from_utf8(self.read_exact(len)?.to_vec()).map_err(|_| AotArtifactError::InvalidUtf8)
792 }
793
794 fn read_i64(&mut self) -> Result<i64, AotArtifactError> {
795 Ok(i64::from_le_bytes(self.read_exact_array::<8>()?))
796 }
797
798 fn read_f64(&mut self) -> Result<f64, AotArtifactError> {
799 Ok(f64::from_le_bytes(self.read_exact_array::<8>()?))
800 }
801
802 fn read_value(&mut self) -> Result<Value, AotArtifactError> {
803 match self.read_u8()? {
804 0 => Ok(Value::Null),
805 1 => Ok(Value::Int(self.read_i64()?)),
806 2 => Ok(Value::Float(self.read_f64()?)),
807 3 => match self.read_u8()? {
808 0 => Ok(Value::Bool(false)),
809 1 => Ok(Value::Bool(true)),
810 other => Err(AotArtifactError::InvalidBool(other)),
811 },
812 4 => Ok(Value::string(self.read_string()?)),
813 5 => {
814 let len = self.read_u32()? as usize;
815 Ok(Value::bytes(self.read_exact(len)?.to_vec()))
816 }
817 6 => {
818 let len = self.read_u32()? as usize;
819 let mut values = Vec::with_capacity(len);
820 for _ in 0..len {
821 values.push(self.read_value()?);
822 }
823 Ok(Value::array(values))
824 }
825 7 => {
826 let len = self.read_u32()? as usize;
827 let mut entries = Vec::with_capacity(len);
828 for _ in 0..len {
829 let key = self.read_value()?;
830 let value = self.read_value()?;
831 entries.push((key, value));
832 }
833 Ok(Value::map(entries))
834 }
835 other => Err(AotArtifactError::InvalidValueTag(other)),
836 }
837 }
838
839 fn is_at_end(&self) -> bool {
840 self.offset == self.bytes.len()
841 }
842}
843
844#[cfg(test)]
845mod tests {
846 use super::*;
847 use crate::{BytecodeBuilder, Program, Value, ValueType};
848
849 #[test]
850 fn aot_artifact_decode_rejects_invalid_magic_and_trailing_bytes() {
851 let mut bc = BytecodeBuilder::new();
852 bc.ret();
853 let mut vm = Vm::new(Program::new(vec![Value::Int(1)], bc.finish()));
854 vm.compile_aot().expect("aot compile should succeed");
855
856 let encoded = vm
857 .encode_aot_artifact()
858 .expect("artifact encode should succeed");
859
860 let mut bad_magic = encoded.clone();
861 bad_magic[0..4].copy_from_slice(b"NOPE");
862 assert!(matches!(
863 vm.load_aot_artifact(&bad_magic),
864 Err(AotArtifactError::InvalidMagic(_))
865 ));
866
867 let mut trailing = encoded;
868 trailing.extend_from_slice(&[1, 2, 3]);
869 assert!(matches!(
870 vm.load_aot_artifact(&trailing),
871 Err(AotArtifactError::TrailingBytes)
872 ));
873 }
874
875 #[test]
876 fn aot_artifact_decode_rejects_incompatible_program_hash() {
877 let mut first_bc = BytecodeBuilder::new();
878 first_bc.ldc(0);
879 first_bc.ret();
880 let mut first_vm = Vm::new(Program::new(vec![Value::Int(1)], first_bc.finish()));
881 first_vm
882 .compile_aot()
883 .expect("first aot compile should succeed");
884 let encoded = first_vm
885 .encode_aot_artifact()
886 .expect("artifact encode should succeed");
887
888 let mut second_bc = BytecodeBuilder::new();
889 second_bc.ldc(0);
890 second_bc.ldc(1);
891 second_bc.add();
892 second_bc.ret();
893 let mut second_vm = Vm::new(Program::new(
894 vec![Value::Int(1), Value::Int(2)],
895 second_bc.finish(),
896 ));
897
898 assert!(matches!(
899 second_vm.load_aot_artifact(&encoded),
900 Err(AotArtifactError::IncompatibleProgramHash { .. })
901 ));
902 }
903
904 #[test]
905 fn aot_artifact_roundtrips_into_standalone_vm() {
906 let mut bc = BytecodeBuilder::new();
907 bc.ldc(0);
908 bc.stloc(3);
909 bc.ldloc(3);
910 bc.ret();
911 let program = Program::with_imports_and_debug(
912 vec![Value::array(vec![
913 Value::Int(1),
914 Value::map(vec![(Value::string("k"), Value::Bool(true))]),
915 ])],
916 bc.finish(),
917 vec![crate::bytecode::HostImport {
918 name: "print".to_string(),
919 arity: 1,
920 return_type: ValueType::Unknown,
921 }],
922 None,
923 )
924 .with_local_count(8);
925 let mut vm = Vm::new(program.clone());
926 vm.compile_aot().expect("aot compile should succeed");
927
928 let encoded = vm
929 .encode_aot_artifact()
930 .expect("artifact encode should succeed");
931 let standalone = Vm::new_from_aot_artifact_with_jit_config(&encoded, JitConfig::default())
932 .expect("standalone artifact should load");
933
934 assert_eq!(standalone.program().local_count, 8);
935 assert_eq!(standalone.program().constants, program.constants);
936 assert_eq!(standalone.program().imports, program.imports);
937 assert_eq!(standalone.program().type_map, program.type_map);
938 assert!(
939 standalone.has_aot_program(),
940 "standalone vm should install aot"
941 );
942 }
943}