Skip to main content

vm/vm/aot/
artifact.rs

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}