Skip to main content

hara_native/vm/
artifact.rs

1//! Alpha persistent encoding for validated VM programs.
2
3use std::collections::HashMap;
4use std::rc::Rc;
5
6use num_bigint::BigInt;
7use num_traits::ToPrimitive;
8use sha2::{Digest, Sha256};
9
10use super::opcode::Instruction;
11use super::program::{CatchEntry, FunctionPrototype, Program, TryEntry};
12use super::source_map::SourceMap;
13#[cfg(test)]
14use crate::core::Value;
15use crate::kernel::{FunctionSchema, Position, SchemaField, SchemaType};
16use crate::lang::data::{Keyword, Metadata, MetadataValue, Symbol};
17
18const MAGIC: &[u8; 4] = b"HBC0";
19
20/// Encodes a program after validating it. Constants use the portable HTA
21/// value codec; unsupported runtime-only values are rejected explicitly.
22pub fn encode_program(program: &Program) -> Result<Vec<u8>, String> {
23    super::validate::validate(program).map_err(|error| error.to_string())?;
24    let mut payload = Writer::default();
25    payload.u16(program.entry);
26    payload.option_string(program.namespace.as_deref())?;
27    payload.len(program.constants.len())?;
28    for value in &program.constants {
29        payload.bytes(&crate::hta::encode(value)?)?;
30    }
31    payload.len(program.var_metadata.len())?;
32    for metadata in &program.var_metadata {
33        write_metadata(&mut payload, metadata)?;
34    }
35    write_schema_map(&mut payload, &program.schema_types)?;
36    write_schema_map(&mut payload, &program.function_types)?;
37    write_schema_map(&mut payload, &program.inferred_function_types)?;
38    payload.len(program.functions.len())?;
39    for function in &program.functions {
40        write_function(&mut payload, function)?;
41    }
42    let digest = Sha256::digest(&payload.bytes);
43    let mut output = MAGIC.to_vec();
44    output.extend_from_slice(
45        &u32::try_from(payload.bytes.len())
46            .map_err(|_| "bytecode artifact is too large")?
47            .to_be_bytes(),
48    );
49    output.extend_from_slice(&payload.bytes);
50    output.extend_from_slice(&digest);
51    Ok(output)
52}
53
54/// Decodes, authenticates, and validates a persistent VM program.
55pub fn decode_program(bytes: &[u8]) -> Result<Program, String> {
56    if !bytes.starts_with(MAGIC) {
57        return Err("bytecode artifact has invalid magic".into());
58    }
59    if bytes.len() < 8 + 32 {
60        return Err("bytecode artifact is truncated".into());
61    }
62    let payload_len = u32::from_be_bytes(bytes[4..8].try_into().unwrap()) as usize;
63    let payload_end = 8usize
64        .checked_add(payload_len)
65        .ok_or("bytecode artifact length overflow")?;
66    if payload_end.checked_add(32) != Some(bytes.len()) {
67        return Err("bytecode artifact length mismatch".into());
68    }
69    let payload = &bytes[8..payload_end];
70    if Sha256::digest(payload)[..] != bytes[payload_end..] {
71        return Err("bytecode artifact checksum mismatch".into());
72    }
73    let mut reader = Reader::new(payload);
74    let entry = reader.u16()?;
75    let namespace = reader.option_string()?;
76    let constants = reader.many(|reader| crate::hta::decode_canonical(reader.bytes()?))?;
77    let var_metadata = reader.many(|reader| read_metadata(reader))?;
78    let schema_types = read_schema_map(&mut reader)?;
79    let function_types = read_schema_map(&mut reader)?;
80    let inferred_function_types = read_schema_map(&mut reader)?;
81    let functions = reader.many(|reader| read_function(reader, 5))?;
82    reader.finish()?;
83    let program = Program {
84        namespace,
85        constants,
86        var_metadata,
87        schema_types,
88        function_types,
89        inferred_function_types,
90        functions,
91        entry,
92    };
93    super::validate::validate(&program).map_err(|error| error.to_string())?;
94    Ok(program)
95}
96
97fn write_function(out: &mut Writer, function: &FunctionPrototype) -> Result<(), String> {
98    out.option_string(function.name.as_deref())?;
99    out.byte(u8::from(function.async_function));
100    out.u16(function.arity);
101    out.byte(u8::from(function.variadic));
102    out.u16(function.capture_count);
103    out.u16(function.local_count);
104    out.u16(function.max_stack);
105    out.len(function.code.len())?;
106    for instruction in &function.code {
107        write_instruction(out, instruction);
108    }
109    out.len(function.source_map.len())?;
110    for index in 0..function.source_map.len() {
111        match function.source_map.position(index) {
112            Some(position) => {
113                out.byte(1);
114                out.usize32(position.offset)?;
115                out.usize32(position.line)?;
116                out.usize32(position.column)?;
117            }
118            None => out.byte(0),
119        }
120    }
121    out.len(function.handlers.len())?;
122    for handler in &function.handlers {
123        out.u32(handler.start);
124        out.u32(handler.end);
125        out.u16(handler.depth);
126        out.len(handler.catches.len())?;
127        for catch in &handler.catches {
128            out.string(&catch.class)?;
129            out.u16(catch.binding);
130            out.u32(catch.target);
131        }
132        out.option_u32(handler.finally);
133        out.option_u16(handler.pending_value);
134        out.option_u16(handler.pending_error);
135    }
136    Ok(())
137}
138
139fn read_function(reader: &mut Reader<'_>, version: u8) -> Result<FunctionPrototype, String> {
140    let name = reader.option_string()?;
141    let async_function = version >= 2 && reader.boolean()?;
142    let arity = reader.u16()?;
143    let variadic = reader.boolean()?;
144    let capture_count = reader.u16()?;
145    let local_count = reader.u16()?;
146    let max_stack = reader.u16()?;
147    let code = reader.many(read_instruction)?;
148    let positions = reader.many(|reader| {
149        if reader.boolean()? {
150            Ok(Some(Position {
151                offset: reader.u32()? as usize,
152                line: reader.u32()? as usize,
153                column: reader.u32()? as usize,
154            }))
155        } else {
156            Ok(None)
157        }
158    })?;
159    let mut source_map = SourceMap::default();
160    for position in positions {
161        source_map.record(position);
162    }
163    let handlers = reader.many(|reader| {
164        let start = reader.u32()?;
165        let end = reader.u32()?;
166        let depth = reader.u16()?;
167        let catches = reader.many(|reader| {
168            Ok(CatchEntry {
169                class: reader.string()?,
170                binding: reader.u16()?,
171                target: reader.u32()?,
172            })
173        })?;
174        Ok(TryEntry {
175            start,
176            end,
177            depth,
178            catches,
179            finally: reader.option_u32()?,
180            pending_value: reader.option_u16()?,
181            pending_error: reader.option_u16()?,
182        })
183    })?;
184    Ok(FunctionPrototype {
185        name,
186        async_function,
187        arity,
188        variadic,
189        capture_count,
190        local_count,
191        max_stack,
192        code,
193        source_map,
194        handlers,
195    })
196}
197
198fn write_instruction(out: &mut Writer, instruction: &Instruction) {
199    use Instruction::*;
200    match instruction {
201        Constant(value) => {
202            out.byte(0);
203            out.u32(*value);
204        }
205        Nil => out.byte(1),
206        True => out.byte(2),
207        False => out.byte(3),
208        LoadLocal(value) => {
209            out.byte(4);
210            out.u16(*value);
211        }
212        StoreLocal(value) => {
213            out.byte(5);
214            out.u16(*value);
215        }
216        Pop => out.byte(6),
217        Dup => out.byte(28),
218        IntrinsicCall { target, argc } => {
219            out.byte(50);
220            out.u32(*target);
221            out.byte(*argc);
222        }
223        Jump(value) => {
224            out.byte(8);
225            out.u32(*value);
226        }
227        JumpIfFalse(value) => {
228            out.byte(9);
229            out.u32(*value);
230        }
231        Closure {
232            prototype,
233            captures,
234        } => {
235            out.byte(10);
236            out.u16(*prototype);
237            out.byte(*captures);
238        }
239        Call { argc } => {
240            out.byte(11);
241            out.byte(*argc);
242        }
243        CallStatic { prototype, argc } => {
244            out.byte(12);
245            out.u16(*prototype);
246            out.byte(*argc);
247        }
248        Throw => out.byte(13),
249        Rethrow => out.byte(14),
250        GetGlobal(value) => {
251            out.byte(15);
252            out.u32(*value);
253        }
254        DefGlobal { name, metadata } => {
255            out.byte(16);
256            out.u32(*name);
257            out.option_u16(*metadata);
258        }
259        SetGlobal(value) => {
260            out.byte(17);
261            out.u32(*value);
262        }
263        VarGlobal(value) => {
264            out.byte(18);
265            out.u32(*value);
266        }
267        DeclareGlobal(value) => {
268            out.byte(19);
269            out.u32(*value);
270        }
271        InstanceOf => out.byte(22),
272        MakeMultiArity { name, count } => {
273            out.byte(23);
274            out.u32(*name);
275            out.byte(*count);
276        }
277        Await => out.byte(26),
278        HostCall => out.byte(27),
279        DotCall { method, argc } => {
280            out.byte(45);
281            out.u32(*method);
282            out.byte(*argc);
283        }
284        BuildVector(count) => {
285            out.byte(29);
286            out.u16(*count);
287        }
288        BuildMap(count) => {
289            out.byte(30);
290            out.u16(*count);
291        }
292        BuildSet(count) => {
293            out.byte(31);
294            out.u16(*count);
295        }
296        DefMacro { name, metadata } => {
297            out.byte(32);
298            out.u32(*name);
299            out.option_u16(*metadata);
300        }
301        BuildList(count) => {
302            out.byte(33);
303            out.u16(*count);
304        }
305        ConcatList(count) => {
306            out.byte(34);
307            out.u16(*count);
308        }
309        ToVector => out.byte(35),
310        IntrinsicValue(target) => {
311            out.byte(51);
312            out.u32(*target);
313        }
314        ProtocolCall { target, argc } => {
315            out.byte(52);
316            out.u32(*target);
317            out.byte(*argc);
318        }
319        BuiltinValue(index) => {
320            out.byte(38);
321            out.u32(*index);
322        }
323        NamespaceValue(index) => {
324            out.byte(53);
325            out.u32(*index);
326        }
327        NamespaceOperation(index) => {
328            out.byte(54);
329            out.u32(*index);
330        }
331        DynamicBind(index) => {
332            out.byte(39);
333            out.u32(*index);
334        }
335        DynamicUnbind(index) => {
336            out.byte(40);
337            out.u32(*index);
338        }
339        Yield => out.byte(46),
340        MutableFieldGet(value) => {
341            out.byte(48);
342            out.u32(*value);
343        }
344        MutableFieldSet(value) => {
345            out.byte(49);
346            out.u32(*value);
347        }
348        Return => out.byte(24),
349    }
350}
351
352fn read_instruction(reader: &mut Reader<'_>) -> Result<Instruction, String> {
353    Ok(match reader.byte()? {
354        0 => Instruction::Constant(reader.u32()?),
355        1 => Instruction::Nil,
356        2 => Instruction::True,
357        3 => Instruction::False,
358        4 => Instruction::LoadLocal(reader.u16()?),
359        5 => Instruction::StoreLocal(reader.u16()?),
360        6 => Instruction::Pop,
361        7 => {
362            return Err(
363                "bytecode artifact uses retired Primitive opcode 7; rebuild required".into(),
364            )
365        }
366        8 => Instruction::Jump(reader.u32()?),
367        9 => Instruction::JumpIfFalse(reader.u32()?),
368        10 => Instruction::Closure {
369            prototype: reader.u16()?,
370            captures: reader.byte()?,
371        },
372        11 => Instruction::Call {
373            argc: reader.byte()?,
374        },
375        12 => Instruction::CallStatic {
376            prototype: reader.u16()?,
377            argc: reader.byte()?,
378        },
379        13 => Instruction::Throw,
380        14 => Instruction::Rethrow,
381        15 => Instruction::GetGlobal(reader.u32()?),
382        16 => Instruction::DefGlobal {
383            name: reader.u32()?,
384            metadata: reader.option_u16()?,
385        },
386        17 => Instruction::SetGlobal(reader.u32()?),
387        18 => Instruction::VarGlobal(reader.u32()?),
388        19 => Instruction::DeclareGlobal(reader.u32()?),
389        20 => {
390            return Err(
391                "bytecode artifact uses retired DefStruct opcode 20; rebuild required".into(),
392            )
393        }
394        21 => {
395            return Err(
396                "bytecode artifact uses retired StructField opcode 21; rebuild required".into(),
397            )
398        }
399        22 => Instruction::InstanceOf,
400        23 => Instruction::MakeMultiArity {
401            name: reader.u32()?,
402            count: reader.byte()?,
403        },
404        24 => Instruction::Return,
405        25 => {
406            return Err(
407                "bytecode artifact uses retired PrimitiveLocalConst opcode 25; rebuild required"
408                    .into(),
409            )
410        }
411        26 => Instruction::Await,
412        27 => Instruction::HostCall,
413        28 => Instruction::Dup,
414        29 => Instruction::BuildVector(reader.u16()?),
415        30 => Instruction::BuildMap(reader.u16()?),
416        31 => Instruction::BuildSet(reader.u16()?),
417        32 => Instruction::DefMacro {
418            name: reader.u32()?,
419            metadata: reader.option_u16()?,
420        },
421        33 => Instruction::BuildList(reader.u16()?),
422        34 => Instruction::ConcatList(reader.u16()?),
423        35 => Instruction::ToVector,
424        37 => {
425            return Err(
426                "bytecode artifact uses retired PrimitiveValue opcode 37; rebuild required".into(),
427            )
428        }
429        38 => Instruction::BuiltinValue(reader.u32()?),
430        39 => Instruction::DynamicBind(reader.u32()?),
431        40 => Instruction::DynamicUnbind(reader.u32()?),
432        41 => {
433            return Err(
434                "bytecode artifact uses retired DefProtocol opcode 41; rebuild required".into(),
435            )
436        }
437        42 => {
438            return Err(
439                "bytecode artifact uses retired ExtendType opcode 42; rebuild required".into(),
440            )
441        }
442        43 => {
443            return Err(
444                "bytecode artifact uses retired DefMulti opcode 43; rebuild required".into(),
445            )
446        }
447        44 => {
448            return Err(
449                "bytecode artifact uses retired DefMethod opcode 44; rebuild required".into(),
450            )
451        }
452        45 => Instruction::DotCall {
453            method: reader.u32()?,
454            argc: reader.byte()?,
455        },
456        46 => Instruction::Yield,
457        47 => {
458            return Err(
459                "bytecode artifact uses retired DefMutable opcode 47; rebuild required".into(),
460            )
461        }
462        48 => Instruction::MutableFieldGet(reader.u32()?),
463        49 => Instruction::MutableFieldSet(reader.u32()?),
464        50 => Instruction::IntrinsicCall {
465            target: reader.u32()?,
466            argc: reader.byte()?,
467        },
468        51 => Instruction::IntrinsicValue(reader.u32()?),
469        52 => Instruction::ProtocolCall {
470            target: reader.u32()?,
471            argc: reader.byte()?,
472        },
473        53 => Instruction::NamespaceValue(reader.u32()?),
474        54 => Instruction::NamespaceOperation(reader.u32()?),
475        _ => return Err("bytecode artifact contains an unknown opcode".into()),
476    })
477}
478
479fn write_metadata(out: &mut Writer, metadata: &Metadata) -> Result<(), String> {
480    out.len(metadata.entries().len())?;
481    for (key, value) in metadata.entries() {
482        write_metadata_value(out, key)?;
483        write_metadata_value(out, value)?;
484    }
485    Ok(())
486}
487
488fn read_metadata(reader: &mut Reader<'_>) -> Result<Rc<Metadata>, String> {
489    let entries =
490        reader.many(|reader| Ok((read_metadata_value(reader)?, read_metadata_value(reader)?)))?;
491    Ok(Metadata::new(entries))
492}
493
494fn write_metadata_value(out: &mut Writer, value: &MetadataValue) -> Result<(), String> {
495    use MetadataValue::*;
496    match value {
497        Nil => out.byte(0),
498        Boolean(v) => {
499            out.byte(1);
500            out.byte(u8::from(*v));
501        }
502        Number(v) => {
503            out.byte(2);
504            out.i64(*v);
505        }
506        Float(v) => {
507            if !v.is_finite() {
508                return Err("non-finite number".into());
509            }
510            out.byte(3);
511            out.u64(v.to_bits());
512        }
513        BigInteger(v) => {
514            if let Some(value) = v.to_i64() {
515                out.byte(2);
516                out.i64(value);
517            } else {
518                out.byte(4);
519                out.string(&v.to_string())?;
520            }
521        }
522        Character(v) => {
523            out.byte(6);
524            out.u32(*v as u32);
525        }
526        Regex(v) => {
527            out.byte(7);
528            out.string(v)?;
529        }
530        Tagged(tag, value) => {
531            out.byte(8);
532            out.string(tag)?;
533            write_metadata_value(out, value)?;
534        }
535        String(v) => {
536            out.byte(9);
537            out.string(v)?;
538        }
539        Keyword(v) => {
540            out.byte(10);
541            out.string(v.as_str())?;
542        }
543        Symbol(v) => {
544            out.byte(11);
545            out.string(v.as_str())?;
546        }
547        Vector(values) => {
548            out.byte(12);
549            write_metadata_values(out, values)?;
550        }
551        List(values) => {
552            out.byte(13);
553            write_metadata_values(out, values)?;
554        }
555        Set(values) => {
556            out.byte(14);
557            write_metadata_values(out, values)?;
558        }
559        Map(values) => {
560            out.byte(15);
561            out.len(values.len())?;
562            for (k, v) in values {
563                write_metadata_value(out, k)?;
564                write_metadata_value(out, v)?;
565            }
566        }
567    }
568    Ok(())
569}
570
571fn write_metadata_values(out: &mut Writer, values: &[MetadataValue]) -> Result<(), String> {
572    out.len(values.len())?;
573    for value in values {
574        write_metadata_value(out, value)?;
575    }
576    Ok(())
577}
578
579fn read_metadata_value(reader: &mut Reader<'_>) -> Result<MetadataValue, String> {
580    Ok(match reader.byte()? {
581        0 => MetadataValue::Nil,
582        1 => MetadataValue::Boolean(reader.boolean()?),
583        2 => MetadataValue::Number(reader.i64()?),
584        3 => {
585            let value = f64::from_bits(reader.u64()?);
586            if !value.is_finite() {
587                return Err("non-finite number".into());
588            }
589            MetadataValue::Float(value)
590        }
591        4 => {
592            let value = BigInt::parse_bytes(reader.string()?.as_bytes(), 10)
593                .ok_or("invalid metadata big integer")?;
594            value
595                .to_i64()
596                .map(MetadataValue::Number)
597                .unwrap_or(MetadataValue::BigInteger(value))
598        }
599        5 => return Err("unsupported metadata tag: decimal".into()),
600        6 => MetadataValue::Character(
601            char::from_u32(reader.u32()?).ok_or("invalid metadata character")?,
602        ),
603        7 => MetadataValue::Regex(reader.string()?),
604        8 => MetadataValue::Tagged(reader.string()?, Box::new(read_metadata_value(reader)?)),
605        9 => MetadataValue::String(reader.string()?),
606        10 => MetadataValue::Keyword(Keyword::from(reader.string()?)),
607        11 => MetadataValue::Symbol(Symbol::from(reader.string()?)),
608        12 => MetadataValue::Vector(reader.many(read_metadata_value)?),
609        13 => MetadataValue::List(reader.many(read_metadata_value)?),
610        14 => MetadataValue::Set(reader.many(read_metadata_value)?),
611        15 => MetadataValue::Map(
612            reader.many(|r| Ok((read_metadata_value(r)?, read_metadata_value(r)?)))?,
613        ),
614        _ => return Err("bytecode artifact contains unknown metadata".into()),
615    })
616}
617
618fn write_schema_map(out: &mut Writer, schemas: &HashMap<String, SchemaType>) -> Result<(), String> {
619    let mut names = schemas.keys().collect::<Vec<_>>();
620    names.sort();
621    out.len(names.len())?;
622    for name in names {
623        out.string(name)?;
624        write_schema_type(out, &schemas[name])?;
625    }
626    Ok(())
627}
628
629fn read_schema_map(reader: &mut Reader<'_>) -> Result<HashMap<String, SchemaType>, String> {
630    let entries = reader.many(|reader| Ok((reader.string()?, read_schema_type(reader)?)))?;
631    let mut schemas = HashMap::with_capacity(entries.len());
632    for (name, schema) in entries {
633        if schemas.insert(name.clone(), schema).is_some() {
634            return Err(format!(
635                "bytecode artifact contains duplicate schema {name}"
636            ));
637        }
638    }
639    Ok(schemas)
640}
641
642fn write_schema_type(out: &mut Writer, schema: &SchemaType) -> Result<(), String> {
643    match schema {
644        SchemaType::Primitive(name) => {
645            out.byte(0);
646            out.string(name)?;
647        }
648        SchemaType::Reference(name) => {
649            out.byte(1);
650            out.string(name)?;
651        }
652        SchemaType::Union(types) => {
653            out.byte(2);
654            write_schema_types(out, types)?;
655        }
656        SchemaType::Vector(item) => {
657            out.byte(3);
658            write_schema_type(out, item)?;
659        }
660        SchemaType::Set(item) => {
661            out.byte(10);
662            write_schema_type(out, item)?;
663        }
664        SchemaType::Tuple(items) => {
665            out.byte(4);
666            write_schema_types(out, items)?;
667        }
668        SchemaType::Map(fields) => {
669            // Keep tag 5 byte-for-byte compatible with existing artifacts.
670            // Property-aware fields use tag 12 so older schema maps remain readable.
671            let property_aware = fields.iter().any(|field| field.properties.is_some());
672            out.byte(if property_aware { 12 } else { 5 });
673            out.len(fields.len())?;
674            for field in fields {
675                write_schema_form(out, &field.name)?;
676                if property_aware {
677                    match &field.properties {
678                        Some(properties) => {
679                            out.byte(1);
680                            write_schema_form(out, properties)?;
681                        }
682                        None => out.byte(0),
683                    }
684                }
685                write_schema_type(out, &field.value_type)?;
686            }
687        }
688        SchemaType::Struct {
689            name,
690            mutable,
691            fields,
692        } => {
693            out.byte(13);
694            out.string(name)?;
695            out.byte(u8::from(*mutable));
696            let property_aware = fields.iter().any(|field| field.properties.is_some());
697            out.byte(u8::from(property_aware));
698            out.len(fields.len())?;
699            for field in fields {
700                write_schema_form(out, &field.name)?;
701                if property_aware {
702                    match &field.properties {
703                        Some(properties) => {
704                            out.byte(1);
705                            write_schema_form(out, properties)?;
706                        }
707                        None => out.byte(0),
708                    }
709                }
710                write_schema_type(out, &field.value_type)?;
711            }
712        }
713        SchemaType::WithProperties { schema, properties } => {
714            out.byte(11);
715            write_schema_type(out, schema)?;
716            write_schema_form(out, properties)?;
717        }
718        SchemaType::Function(arities) => {
719            out.byte(6);
720            out.len(arities.len())?;
721            for arity in arities {
722                write_schema_types(out, &arity.fixed)?;
723                match &arity.rest {
724                    Some(rest) => {
725                        out.byte(1);
726                        write_schema_type(out, rest)?;
727                    }
728                    None => out.byte(0),
729                }
730                write_schema_type(out, &arity.output)?;
731            }
732        }
733        SchemaType::Enum(values) => {
734            out.byte(7);
735            write_schema_forms(out, values)?;
736        }
737        SchemaType::Extension { head, arguments } => {
738            out.byte(8);
739            out.string(head)?;
740            write_schema_forms(out, arguments)?;
741        }
742        SchemaType::Unknown(surface) => {
743            out.byte(9);
744            write_schema_form(out, surface)?;
745        }
746    }
747    Ok(())
748}
749
750fn read_schema_type(reader: &mut Reader<'_>) -> Result<SchemaType, String> {
751    Ok(match reader.byte()? {
752        0 => SchemaType::Primitive(reader.string()?),
753        1 => SchemaType::Reference(reader.string()?),
754        2 => SchemaType::Union(reader.many(read_schema_type)?),
755        3 => SchemaType::Vector(Box::new(read_schema_type(reader)?)),
756        4 => SchemaType::Tuple(reader.many(read_schema_type)?),
757        5 => SchemaType::Map(reader.many(|reader| {
758            Ok(SchemaField {
759                name: read_schema_form(reader)?,
760                properties: None,
761                value_type: read_schema_type(reader)?,
762            })
763        })?),
764        6 => SchemaType::Function(reader.many(|reader| {
765            let fixed = reader.many(read_schema_type)?;
766            let rest = if reader.boolean()? {
767                Some(Box::new(read_schema_type(reader)?))
768            } else {
769                None
770            };
771            Ok(FunctionSchema {
772                fixed,
773                rest,
774                output: Box::new(read_schema_type(reader)?),
775            })
776        })?),
777        7 => SchemaType::Enum(read_schema_forms(reader)?),
778        8 => SchemaType::Extension {
779            head: reader.string()?,
780            arguments: read_schema_forms(reader)?,
781        },
782        9 => SchemaType::Unknown(read_schema_form(reader)?),
783        10 => SchemaType::Set(Box::new(read_schema_type(reader)?)),
784        11 => SchemaType::WithProperties {
785            schema: Box::new(read_schema_type(reader)?),
786            properties: read_schema_form(reader)?,
787        },
788        12 => SchemaType::Map(reader.many(|reader| {
789            let name = read_schema_form(reader)?;
790            let properties = if reader.boolean()? {
791                Some(read_schema_form(reader)?)
792            } else {
793                None
794            };
795            Ok(SchemaField {
796                name,
797                properties,
798                value_type: read_schema_type(reader)?,
799            })
800        })?),
801        13 => {
802            let name = reader.string()?;
803            let mutable = reader.boolean()?;
804            let property_aware = reader.boolean()?;
805            SchemaType::Struct {
806                name,
807                mutable,
808                fields: reader.many(|reader| {
809                    let name = read_schema_form(reader)?;
810                    let properties = if property_aware {
811                        if reader.boolean()? {
812                            Some(read_schema_form(reader)?)
813                        } else {
814                            None
815                        }
816                    } else {
817                        None
818                    };
819                    Ok(SchemaField {
820                        name,
821                        properties,
822                        value_type: read_schema_type(reader)?,
823                    })
824                })?,
825            }
826        }
827        _ => return Err("bytecode artifact contains unknown schema type".into()),
828    })
829}
830
831fn write_schema_types(out: &mut Writer, types: &[SchemaType]) -> Result<(), String> {
832    out.len(types.len())?;
833    for schema in types {
834        write_schema_type(out, schema)?;
835    }
836    Ok(())
837}
838
839fn write_schema_forms(out: &mut Writer, forms: &[crate::kernel::Form]) -> Result<(), String> {
840    out.len(forms.len())?;
841    for form in forms {
842        write_schema_form(out, form)?;
843    }
844    Ok(())
845}
846
847fn read_schema_forms(reader: &mut Reader<'_>) -> Result<Vec<crate::kernel::Form>, String> {
848    reader.many(read_schema_form)
849}
850
851fn write_schema_form(out: &mut Writer, form: &crate::kernel::Form) -> Result<(), String> {
852    out.string(&form.to_string())
853}
854
855fn read_schema_form(reader: &mut Reader<'_>) -> Result<crate::kernel::Form, String> {
856    crate::kernel::parse(&reader.string()?)
857        .map_err(|error| format!("bytecode artifact contains invalid schema form: {error}"))
858}
859
860#[derive(Default)]
861struct Writer {
862    bytes: Vec<u8>,
863}
864impl Writer {
865    fn byte(&mut self, value: u8) {
866        self.bytes.push(value);
867    }
868    fn u16(&mut self, value: u16) {
869        self.bytes.extend_from_slice(&value.to_be_bytes());
870    }
871    fn u32(&mut self, value: u32) {
872        self.bytes.extend_from_slice(&value.to_be_bytes());
873    }
874    fn u64(&mut self, value: u64) {
875        self.bytes.extend_from_slice(&value.to_be_bytes());
876    }
877    fn i64(&mut self, value: i64) {
878        self.bytes.extend_from_slice(&value.to_be_bytes());
879    }
880    fn usize32(&mut self, value: usize) -> Result<(), String> {
881        self.u32(u32::try_from(value).map_err(|_| "bytecode field is too large")?);
882        Ok(())
883    }
884    fn len(&mut self, value: usize) -> Result<(), String> {
885        self.usize32(value)
886    }
887    fn bytes(&mut self, value: &[u8]) -> Result<(), String> {
888        self.len(value.len())?;
889        self.bytes.extend_from_slice(value);
890        Ok(())
891    }
892    fn string(&mut self, value: &str) -> Result<(), String> {
893        self.bytes(value.as_bytes())
894    }
895    fn option_string(&mut self, value: Option<&str>) -> Result<(), String> {
896        match value {
897            Some(v) => {
898                self.byte(1);
899                self.string(v)?;
900            }
901            None => self.byte(0),
902        };
903        Ok(())
904    }
905    fn option_u16(&mut self, value: Option<u16>) {
906        match value {
907            Some(v) => {
908                self.byte(1);
909                self.u16(v);
910            }
911            None => self.byte(0),
912        }
913    }
914    fn option_u32(&mut self, value: Option<u32>) {
915        match value {
916            Some(v) => {
917                self.byte(1);
918                self.u32(v);
919            }
920            None => self.byte(0),
921        }
922    }
923}
924
925struct Reader<'a> {
926    bytes: &'a [u8],
927    cursor: usize,
928}
929impl<'a> Reader<'a> {
930    fn new(bytes: &'a [u8]) -> Self {
931        Self { bytes, cursor: 0 }
932    }
933    fn take(&mut self, size: usize) -> Result<&'a [u8], String> {
934        let end = self
935            .cursor
936            .checked_add(size)
937            .ok_or("bytecode artifact length overflow")?;
938        if end > self.bytes.len() {
939            return Err("bytecode artifact is truncated".into());
940        }
941        let value = &self.bytes[self.cursor..end];
942        self.cursor = end;
943        Ok(value)
944    }
945    fn byte(&mut self) -> Result<u8, String> {
946        Ok(self.take(1)?[0])
947    }
948    fn boolean(&mut self) -> Result<bool, String> {
949        match self.byte()? {
950            0 => Ok(false),
951            1 => Ok(true),
952            _ => Err("bytecode artifact contains invalid boolean".into()),
953        }
954    }
955    fn u16(&mut self) -> Result<u16, String> {
956        Ok(u16::from_be_bytes(self.take(2)?.try_into().unwrap()))
957    }
958    fn u32(&mut self) -> Result<u32, String> {
959        Ok(u32::from_be_bytes(self.take(4)?.try_into().unwrap()))
960    }
961    fn u64(&mut self) -> Result<u64, String> {
962        Ok(u64::from_be_bytes(self.take(8)?.try_into().unwrap()))
963    }
964    fn i64(&mut self) -> Result<i64, String> {
965        Ok(i64::from_be_bytes(self.take(8)?.try_into().unwrap()))
966    }
967    fn bytes(&mut self) -> Result<&'a [u8], String> {
968        let size = self.u32()? as usize;
969        self.take(size)
970    }
971    fn string(&mut self) -> Result<String, String> {
972        String::from_utf8(self.bytes()?.to_vec())
973            .map_err(|_| "bytecode artifact contains invalid UTF-8".into())
974    }
975    fn option_string(&mut self) -> Result<Option<String>, String> {
976        if self.boolean()? {
977            Ok(Some(self.string()?))
978        } else {
979            Ok(None)
980        }
981    }
982    fn option_u16(&mut self) -> Result<Option<u16>, String> {
983        if self.boolean()? {
984            Ok(Some(self.u16()?))
985        } else {
986            Ok(None)
987        }
988    }
989    fn option_u32(&mut self) -> Result<Option<u32>, String> {
990        if self.boolean()? {
991            Ok(Some(self.u32()?))
992        } else {
993            Ok(None)
994        }
995    }
996    fn many<T>(
997        &mut self,
998        mut read: impl FnMut(&mut Reader<'a>) -> Result<T, String>,
999    ) -> Result<Vec<T>, String> {
1000        let size = self.u32()? as usize;
1001        let mut values = Vec::with_capacity(size.min(4096));
1002        for _ in 0..size {
1003            values.push(read(self)?);
1004        }
1005        Ok(values)
1006    }
1007    fn finish(&self) -> Result<(), String> {
1008        if self.cursor == self.bytes.len() {
1009            Ok(())
1010        } else {
1011            Err("bytecode artifact has trailing payload bytes".into())
1012        }
1013    }
1014}
1015
1016#[cfg(test)]
1017#[path = "artifact/tests.rs"]
1018mod tests;