Skip to main content

hara_native/wasm_binding/
adapter.rs

1#![cfg(not(target_arch = "wasm32"))]
2
3use sha2::{Digest, Sha256};
4use wasm_encoder::{
5    ConstExpr, EntityType, ExportKind, ExportSection, Function, FunctionSection, GlobalSection,
6    GlobalType, ImportSection, Instruction, MemArg, MemorySection, MemoryType, Module, TypeSection,
7    ValType,
8};
9
10use crate::kernel::Form;
11
12use super::{inspect_direct, BindingFunction, HaraValueType, WasmInterface, WasmValueType};
13
14pub const ADAPTER_MANIFEST_SCHEMA: &str = "hara.wasm-adapter/0-alpha";
15const ADAPTER_TARGET: &str = "core.v1-forward";
16const HTA_ADAPTER_TARGET: &str = "hta.v1";
17const LIBRARY_IMPORT_MODULE: &str = "hara/library";
18
19/// A deterministic adapter module and the manifest describing its composition.
20///
21/// The first adapter revision is deliberately a scalar forwarding boundary:
22/// the adapter imports the verified library exports under one stable module
23/// name and exports the Hara-facing names. Rich memory and HTA lifecycle
24/// operations remain explicit follow-up revisions rather than guessed from
25/// machine-level values.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct AdapterArtifact {
28    pub bytes: Vec<u8>,
29    pub manifest: String,
30    pub module_digest: String,
31    pub interface_digest: String,
32    pub adapter_digest: String,
33}
34
35/// Generate the portable scalar adapter for a verified library/interface pair.
36///
37/// Inspection only parses the module bytes. It never instantiates the wrapped
38/// library or runs its start function.
39pub fn generate_adapter(
40    module_bytes: &[u8],
41    interface: &WasmInterface,
42) -> Result<AdapterArtifact, String> {
43    if interface.exports.iter().any(|export| export.asynchronous) {
44        return Err(
45            "wasm-adapter/feature-unsupported: asynchronous exports require the HTA adapter".into(),
46        );
47    }
48    let inspection = inspect_direct(module_bytes)?;
49    if inspection.start.is_some() {
50        return Err("wasm-adapter/start-denied: wrapped module declares a start function".into());
51    }
52    interface.verify_direct(&inspection)?;
53    let exports = ordered_exports(interface)?;
54
55    let bytes = emit_forwarder(&exports)?;
56    let module_digest = digest(module_bytes);
57    let interface_digest = interface.digest();
58    let adapter_digest = digest(&bytes);
59    let manifest = adapter_manifest(
60        interface,
61        &module_digest,
62        &interface_digest,
63        &adapter_digest,
64        &exports,
65    );
66
67    Ok(AdapterArtifact {
68        bytes,
69        manifest,
70        module_digest,
71        interface_digest,
72        adapter_digest,
73    })
74}
75
76/// Generate the HTA package adapter for scalar bindings.
77///
78/// The adapter owns the HTA task/event boundary and imports only the verified
79/// library functions. Memory and handle lowering remain separate binding
80/// revisions; silently treating those values as scalars would violate the
81/// interface ownership contract.
82pub fn generate_hta_adapter(
83    module_bytes: &[u8],
84    interface: &WasmInterface,
85) -> Result<AdapterArtifact, String> {
86    let inspection = inspect_direct(module_bytes)?;
87    if inspection.start.is_some() {
88        return Err("wasm-adapter/start-denied: wrapped module declares a start function".into());
89    }
90    verify_hta_scalar(interface, &inspection)?;
91    let exports = ordered_exports(interface)?;
92    let bytes = emit_hta_forwarder(&exports)?;
93    let module_digest = digest(module_bytes);
94    let interface_digest = interface.digest();
95    let adapter_digest = digest(&bytes);
96    let manifest = hta_adapter_manifest(
97        interface,
98        &module_digest,
99        &interface_digest,
100        &adapter_digest,
101        &exports,
102    );
103
104    Ok(AdapterArtifact {
105        bytes,
106        manifest,
107        module_digest,
108        interface_digest,
109        adapter_digest,
110    })
111}
112
113fn ordered_exports(interface: &WasmInterface) -> Result<Vec<BindingFunction>, String> {
114    let mut exports = interface.exports.clone();
115    exports.sort_by(|left, right| left.name.cmp(&right.name));
116    for pair in exports.windows(2) {
117        if pair[0].name == pair[1].name {
118            return Err(format!(
119                "wasm-adapter/export-ambiguous: duplicate Hara export {}",
120                pair[0].name
121            ));
122        }
123    }
124    Ok(exports)
125}
126
127pub fn verify_hta_scalar(
128    interface: &WasmInterface,
129    inspection: &super::DirectWasmInspection,
130) -> Result<(), String> {
131    if interface.memory.is_some() {
132        return Err(
133            "wasm-adapter/feature-unsupported: memory lowering requires a later HTA revision"
134                .into(),
135        );
136    }
137    if !interface.capabilities.is_empty()
138        || interface
139            .exports
140            .iter()
141            .any(|export| !export.capabilities.is_empty())
142    {
143        return Err(
144            "wasm-adapter/capability-denied: scalar adapters cannot require host capabilities"
145                .into(),
146        );
147    }
148    if interface.exports.iter().any(|export| {
149        export.errors.is_some()
150            || export
151                .arguments
152                .iter()
153                .any(|argument| argument.hara_type.direct_wasm_type().is_none())
154            || interface
155                .exports
156                .iter()
157                .any(|candidate| candidate.returns.hara_type.direct_wasm_type().is_none())
158    }) {
159        return Err(
160            "wasm-adapter/feature-unsupported: non-scalar and error mappings require a later HTA revision"
161                .into(),
162        );
163    }
164    let discovered = inspection
165        .direct_exports()
166        .map_err(|error| format!("wasm-adapter/module-incompatible: {error}"))?
167        .into_iter()
168        .collect::<std::collections::BTreeMap<_, _>>();
169    let mut raw_names = std::collections::BTreeSet::new();
170    for export in &interface.exports {
171        if !raw_names.insert(export.wasm_export.as_str()) {
172            return Err(format!(
173                "wasm-adapter/export-ambiguous: multiple Hara exports map to {}",
174                export.wasm_export
175            ));
176        }
177        let found = discovered.get(&export.wasm_export).ok_or_else(|| {
178            format!(
179                "wasm-adapter/export-missing: {} maps to absent Wasm export {}",
180                export.name, export.wasm_export
181            )
182        })?;
183        let expected = crate::extension::ExtensionExport {
184            arguments: export
185                .arguments
186                .iter()
187                .map(|argument| argument.wasm_type.as_keyword().to_owned())
188                .collect(),
189            returns: export.returns.wasm_type.as_keyword().to_owned(),
190            asynchronous: false,
191            raw_export: None,
192        };
193        if found != &expected {
194            return Err(format!(
195                "wasm-adapter/signature-mismatch: {} -> {} expected {:?}, found {:?}",
196                export.name, export.wasm_export, expected, found
197            ));
198        }
199    }
200    Ok(())
201}
202
203fn emit_forwarder(exports: &[BindingFunction]) -> Result<Vec<u8>, String> {
204    let mut module = Module::new();
205    let mut types = TypeSection::new();
206
207    for export in exports {
208        types.function(
209            export
210                .arguments
211                .iter()
212                .map(|argument| val_type(argument.wasm_type)),
213            result_types(export.returns.wasm_type),
214        );
215    }
216    module.section(&types);
217
218    let mut imports = ImportSection::new();
219    for (index, export) in exports.iter().enumerate() {
220        imports.import(
221            LIBRARY_IMPORT_MODULE,
222            &export.wasm_export,
223            EntityType::Function(index as u32),
224        );
225    }
226    module.section(&imports);
227
228    let mut functions = FunctionSection::new();
229    for index in 0..exports.len() {
230        functions.function(index as u32);
231    }
232    module.section(&functions);
233
234    let mut exports_section = ExportSection::new();
235    let import_count = exports.len() as u32;
236    for (index, export) in exports.iter().enumerate() {
237        exports_section.export(&export.name, ExportKind::Func, import_count + index as u32);
238    }
239    module.section(&exports_section);
240
241    let mut code = wasm_encoder::CodeSection::new();
242    for (index, export) in exports.iter().enumerate() {
243        let mut function = Function::new([]);
244        for argument in 0..export.arguments.len() {
245            function.instruction(&Instruction::LocalGet(argument as u32));
246        }
247        function.instruction(&Instruction::Call(index as u32));
248        function.instruction(&Instruction::End);
249        code.function(&function);
250    }
251    module.section(&code);
252    Ok(module.finish())
253}
254
255fn emit_hta_forwarder(exports: &[BindingFunction]) -> Result<Vec<u8>, String> {
256    let mut module = Module::new();
257    let mut types = TypeSection::new();
258    for export in exports {
259        types.function(
260            export
261                .arguments
262                .iter()
263                .map(|argument| val_type(argument.wasm_type)),
264            result_types(export.returns.wasm_type),
265        );
266    }
267    let import_type_count = exports.len() as u32;
268    let lifecycle_types = [
269        ([ValType::I32].as_slice(), [ValType::I32].as_slice()),
270        ([ValType::I32, ValType::I32].as_slice(), [].as_slice()),
271        ([].as_slice(), [ValType::I32].as_slice()),
272        (
273            [ValType::I32, ValType::I32].as_slice(),
274            [ValType::I64].as_slice(),
275        ),
276        ([].as_slice(), [ValType::I64].as_slice()),
277        (
278            [ValType::I32, ValType::I32].as_slice(),
279            [ValType::I32].as_slice(),
280        ),
281        ([ValType::I64].as_slice(), [ValType::I32].as_slice()),
282        ([ValType::I64].as_slice(), [ValType::I32].as_slice()),
283        (
284            [ValType::I32, ValType::I32].as_slice(),
285            [ValType::I32].as_slice(),
286        ),
287    ];
288    for (arguments, results) in lifecycle_types {
289        types.function(arguments.iter().copied(), results.iter().copied());
290    }
291    module.section(&types);
292
293    let mut imports = ImportSection::new();
294    for (index, export) in exports.iter().enumerate() {
295        imports.import(
296            LIBRARY_IMPORT_MODULE,
297            &export.wasm_export,
298            EntityType::Function(index as u32),
299        );
300    }
301    module.section(&imports);
302
303    let alloc = import_type_count;
304    let dealloc = alloc + 1;
305    let abi_version = alloc + 2;
306    let start = alloc + 3;
307    let next_event = alloc + 4;
308    let deliver = alloc + 5;
309    let cancel = alloc + 6;
310    let drop_task = alloc + 7;
311    let release = alloc + 8;
312    let mut functions = FunctionSection::new();
313    for type_index in import_type_count..import_type_count + 9 {
314        functions.function(type_index);
315    }
316    module.section(&functions);
317
318    let mut memories = MemorySection::new();
319    memories.memory(MemoryType {
320        minimum: 2,
321        maximum: Some(1024),
322        memory64: false,
323        shared: false,
324    });
325    module.section(&memories);
326
327    let mut globals = GlobalSection::new();
328    globals.global(
329        GlobalType {
330            val_type: ValType::I32,
331            mutable: true,
332        },
333        &ConstExpr::i32_const(1024),
334    );
335    for value in [0, 0, 0, 1, 0] {
336        globals.global(
337            GlobalType {
338                val_type: ValType::I32,
339                mutable: true,
340            },
341            &ConstExpr::i32_const(value),
342        );
343    }
344    module.section(&globals);
345
346    let mut exports_section = ExportSection::new();
347    for (name, index) in [
348        ("hta_alloc", alloc),
349        ("hta_dealloc", dealloc),
350        ("hta_abi_version", abi_version),
351        ("hta_start", start),
352        ("hta_next_event", next_event),
353        ("hta_deliver", deliver),
354        ("hta_cancel", cancel),
355        ("hta_drop_task", drop_task),
356        ("hta_release", release),
357    ] {
358        exports_section.export(name, ExportKind::Func, index);
359    }
360    exports_section.export("memory", ExportKind::Memory, 0);
361    module.section(&exports_section);
362
363    let mut code = wasm_encoder::CodeSection::new();
364    code.function(&emit_alloc());
365    code.function(&emit_noop(&[ValType::I32, ValType::I32], &[]));
366    code.function(&emit_abi_version());
367    code.function(&emit_start(exports, alloc));
368    code.function(&emit_next_event());
369    code.function(&emit_noop(&[ValType::I32, ValType::I32], &[ValType::I32]));
370    code.function(&emit_noop(&[ValType::I64], &[ValType::I32]));
371    code.function(&emit_noop(&[ValType::I64], &[ValType::I32]));
372    code.function(&emit_noop(&[ValType::I32, ValType::I32], &[ValType::I32]));
373    module.section(&code);
374    Ok(module.finish())
375}
376
377fn emit_alloc() -> Function {
378    let mut function = Function::new([(1, ValType::I32)]);
379    function.instruction(&Instruction::GlobalGet(0));
380    function.instruction(&Instruction::LocalSet(1));
381    function.instruction(&Instruction::GlobalGet(0));
382    function.instruction(&Instruction::LocalGet(0));
383    function.instruction(&Instruction::I32Add);
384    function.instruction(&Instruction::GlobalSet(0));
385    function.instruction(&Instruction::LocalGet(1));
386    function.instruction(&Instruction::End);
387    function
388}
389
390fn emit_noop(parameters: &[ValType], results: &[ValType]) -> Function {
391    let mut function = Function::new([]);
392    for (index, _) in parameters.iter().enumerate() {
393        let _ = index;
394    }
395    if let Some(result) = results.first() {
396        function.instruction(match result {
397            ValType::I32 => &Instruction::I32Const(0),
398            ValType::I64 => &Instruction::I64Const(0),
399            _ => unreachable!("HTA lifecycle uses only integer results"),
400        });
401    }
402    function.instruction(&Instruction::End);
403    function
404}
405
406fn emit_abi_version() -> Function {
407    let mut function = Function::new([]);
408    function.instruction(&Instruction::I32Const(1));
409    function.instruction(&Instruction::End);
410    function
411}
412
413fn emit_start(exports: &[BindingFunction], alloc: u32) -> Function {
414    let mut function = Function::new([(1, ValType::I32), (1, ValType::I64), (1, ValType::I64)]);
415    function.instruction(&Instruction::I32Const(64));
416    function.instruction(&Instruction::Call(alloc));
417    function.instruction(&Instruction::LocalSet(2));
418    function.instruction(&Instruction::I32Const(0));
419    function.instruction(&Instruction::GlobalSet(1));
420    function.instruction(&Instruction::I32Const(0));
421    function.instruction(&Instruction::GlobalSet(2));
422    function.instruction(&Instruction::I32Const(0));
423    function.instruction(&Instruction::GlobalSet(5));
424
425    for (index, export) in exports.iter().enumerate() {
426        let operation = export.operation.as_deref().unwrap_or(&export.name);
427        let name = operation.as_bytes();
428        let mut checks = Vec::new();
429        for (offset, byte) in b"HTA0".iter().enumerate() {
430            checks.push(byte_check(0, offset as u32, *byte));
431        }
432        for (offset, byte) in [(4, 9), (9, 4), (14 + name.len(), 9)] {
433            checks.push(byte_check(0, offset as u32, byte));
434        }
435        for (offset, value) in [
436            (5, 0),
437            (6, 0),
438            (7, 0),
439            (8, 2),
440            (10, ((name.len() as u32 >> 24) & 0xff) as u8),
441            (11, ((name.len() as u32 >> 16) & 0xff) as u8),
442            (12, ((name.len() as u32 >> 8) & 0xff) as u8),
443            (13, (name.len() as u32 & 0xff) as u8),
444            (15 + name.len(), 0),
445            (16 + name.len(), 0),
446            (17 + name.len(), 0),
447            (18 + name.len(), export.arguments.len() as u8),
448        ] {
449            checks.push(byte_check(0, offset as u32, value));
450        }
451        for (offset, byte) in name.iter().enumerate() {
452            checks.push(byte_check(0, 14 + offset as u32, *byte));
453        }
454        checks.push(vec![
455            Instruction::LocalGet(1),
456            Instruction::I32Const(expected_frame_size(export, name.len()) as i32),
457            Instruction::I32Eq,
458        ]);
459        for (index, check) in checks.into_iter().enumerate() {
460            for instruction in check {
461                function.instruction(&instruction);
462            }
463            if index != 0 {
464                function.instruction(&Instruction::I32And);
465            }
466        }
467        function.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
468        function.instruction(&Instruction::I32Const(1));
469        function.instruction(&Instruction::GlobalSet(5));
470        let mut offset = 19 + name.len() as u32;
471        for argument in &export.arguments {
472            decode_argument(&mut function, argument, offset);
473            offset += encoded_size(argument);
474        }
475        function.instruction(&Instruction::Call(index as u32));
476        encode_result(&mut function, &export.returns);
477        function.instruction(&Instruction::End);
478    }
479
480    function.instruction(&Instruction::GlobalGet(5));
481    function.instruction(&Instruction::If(wasm_encoder::BlockType::Result(
482        ValType::I64,
483    )));
484    function.instruction(&Instruction::LocalGet(2));
485    function.instruction(&Instruction::GlobalSet(1));
486    store_byte(&mut function, 2, 0, b'H');
487    store_byte(&mut function, 2, 1, b'T');
488    store_byte(&mut function, 2, 2, b'A');
489    store_byte(&mut function, 2, 3, b'0');
490    store_byte(&mut function, 2, 4, 9);
491    store_i32_constant(&mut function, 2, 5, 3);
492    store_byte(&mut function, 2, 9, 3);
493    store_i64_constant(&mut function, 2, 10, 0);
494    store_byte(&mut function, 2, 18, 3);
495    store_i64_constant(&mut function, 2, 19, 1);
496    store_byte_global(&mut function, 2, 27, 3);
497    function.instruction(&Instruction::GlobalGet(4));
498    function.instruction(&Instruction::I32Const(27));
499    function.instruction(&Instruction::I32Add);
500    function.instruction(&Instruction::GlobalSet(2));
501    for offset in 0..8 {
502        store_i64_byte_from_local(&mut function, 2, 4, 28 + offset, 7 - offset);
503    }
504    function.instruction(&Instruction::I64Const(1));
505    function.instruction(&Instruction::Else);
506    function.instruction(&Instruction::I64Const(0));
507    function.instruction(&Instruction::End);
508    function.instruction(&Instruction::End);
509    function
510}
511
512fn expected_frame_size(export: &BindingFunction, operation_length: usize) -> u32 {
513    19 + operation_length as u32 + export.arguments.iter().map(encoded_size).sum::<u32>()
514}
515
516fn store_i32_constant(function: &mut Function, pointer: u32, offset: u32, value: i32) {
517    for byte in 0..4 {
518        function.instruction(&Instruction::LocalGet(pointer));
519        function.instruction(&Instruction::I32Const((offset + byte) as i32));
520        function.instruction(&Instruction::I32Add);
521        function.instruction(&Instruction::I32Const((value >> ((3 - byte) * 8)) & 0xff));
522        function.instruction(&Instruction::I32Store8(MemArg {
523            offset: 0,
524            align: 0,
525            memory_index: 0,
526        }));
527    }
528}
529
530fn byte_check(pointer: u32, offset: u32, value: u8) -> Vec<Instruction<'static>> {
531    let mut instructions = Vec::with_capacity(3);
532    load_byte(&mut instructions, pointer, offset);
533    instructions.push(Instruction::I32Const(i32::from(value)));
534    instructions.push(Instruction::I32Eq);
535    instructions
536}
537
538fn emit_next_event() -> Function {
539    let mut function = Function::new([(1, ValType::I32), (1, ValType::I32)]);
540    function.instruction(&Instruction::GlobalGet(1));
541    function.instruction(&Instruction::LocalTee(0));
542    function.instruction(&Instruction::I32Eqz);
543    function.instruction(&Instruction::If(wasm_encoder::BlockType::Result(
544        ValType::I64,
545    )));
546    function.instruction(&Instruction::I64Const(0));
547    function.instruction(&Instruction::Else);
548    function.instruction(&Instruction::GlobalGet(2));
549    function.instruction(&Instruction::LocalSet(1));
550    function.instruction(&Instruction::I32Const(0));
551    function.instruction(&Instruction::GlobalSet(1));
552    function.instruction(&Instruction::LocalGet(0));
553    function.instruction(&Instruction::I64ExtendI32U);
554    function.instruction(&Instruction::I64Const(32));
555    function.instruction(&Instruction::I64Shl);
556    function.instruction(&Instruction::LocalGet(1));
557    function.instruction(&Instruction::I64ExtendI32U);
558    function.instruction(&Instruction::I64Or);
559    function.instruction(&Instruction::End);
560    function.instruction(&Instruction::End);
561    function
562}
563
564fn decode_argument(function: &mut Function, argument: &super::BindingParameter, offset: u32) {
565    match argument.hara_type {
566        HaraValueType::Boolean => {
567            load_byte_into(function, 0, offset);
568            function.instruction(&Instruction::I32Const(2));
569            function.instruction(&Instruction::I32Eq);
570        }
571        HaraValueType::I32 | HaraValueType::I64 => {
572            load_i64_be(function, 0, offset + 1);
573            if argument.wasm_type == WasmValueType::I32 {
574                function.instruction(&Instruction::I32WrapI64);
575            }
576        }
577        HaraValueType::F32 | HaraValueType::F64 => {
578            load_i64_be(function, 0, offset + 1);
579            function.instruction(&Instruction::F64ReinterpretI64);
580            if argument.wasm_type == WasmValueType::F32 {
581                function.instruction(&Instruction::F32DemoteF64);
582            }
583        }
584        _ => unreachable!("non-scalar HTA arguments are rejected before emission"),
585    }
586}
587
588fn encode_result(function: &mut Function, result: &super::BindingResult) {
589    match result.hara_type {
590        HaraValueType::Boolean => {
591            function.instruction(&Instruction::I64ExtendI32S);
592            function.instruction(&Instruction::LocalSet(3));
593            function.instruction(&Instruction::LocalGet(3));
594            function.instruction(&Instruction::I32WrapI64);
595            function.instruction(&Instruction::I32Const(1));
596            function.instruction(&Instruction::I32Add);
597            function.instruction(&Instruction::GlobalSet(3));
598            function.instruction(&Instruction::I32Const(1));
599            function.instruction(&Instruction::GlobalSet(4));
600        }
601        HaraValueType::I32 => {
602            function.instruction(&Instruction::I64ExtendI32S);
603            function.instruction(&Instruction::LocalSet(3));
604            set_result_metadata(function, 3, 9);
605        }
606        HaraValueType::I64 => {
607            function.instruction(&Instruction::LocalSet(3));
608            set_result_metadata(function, 3, 9);
609        }
610        HaraValueType::F32 => {
611            function.instruction(&Instruction::F64PromoteF32);
612            function.instruction(&Instruction::I64ReinterpretF64);
613            function.instruction(&Instruction::LocalSet(3));
614            set_result_metadata(function, 15, 9);
615        }
616        HaraValueType::F64 => {
617            function.instruction(&Instruction::I64ReinterpretF64);
618            function.instruction(&Instruction::LocalSet(3));
619            set_result_metadata(function, 15, 9);
620        }
621        HaraValueType::Void => {
622            function.instruction(&Instruction::I64Const(0));
623            function.instruction(&Instruction::LocalSet(3));
624            set_result_metadata(function, 0, 1);
625        }
626        _ => unreachable!("non-scalar HTA results are rejected before emission"),
627    }
628    function.instruction(&Instruction::LocalGet(3));
629    function.instruction(&Instruction::LocalSet(4));
630}
631
632fn set_result_metadata(function: &mut Function, tag: i32, size: i32) {
633    function.instruction(&Instruction::I32Const(tag));
634    function.instruction(&Instruction::GlobalSet(3));
635    function.instruction(&Instruction::I32Const(size));
636    function.instruction(&Instruction::GlobalSet(4));
637}
638
639fn encoded_size(argument: &super::BindingParameter) -> u32 {
640    match argument.hara_type {
641        HaraValueType::Boolean => 1,
642        _ => 9,
643    }
644}
645
646fn load_byte(instructions: &mut Vec<Instruction<'static>>, pointer: u32, offset: u32) {
647    instructions.push(Instruction::LocalGet(pointer));
648    instructions.push(Instruction::I32Const(offset as i32));
649    instructions.push(Instruction::I32Add);
650    instructions.push(Instruction::I32Load8U(MemArg {
651        offset: 0,
652        align: 0,
653        memory_index: 0,
654    }));
655}
656
657fn load_i64_be(function: &mut Function, pointer: u32, offset: u32) {
658    function.instruction(&Instruction::I64Const(0));
659    for byte in 0..8 {
660        load_byte_into(function, pointer, offset + byte);
661        function.instruction(&Instruction::I64ExtendI32U);
662        function.instruction(&Instruction::I64Const(i64::from((7 - byte) * 8)));
663        function.instruction(&Instruction::I64Shl);
664        function.instruction(&Instruction::I64Or);
665    }
666}
667
668fn load_byte_into(function: &mut Function, pointer: u32, offset: u32) {
669    function.instruction(&Instruction::LocalGet(pointer));
670    function.instruction(&Instruction::I32Const(offset as i32));
671    function.instruction(&Instruction::I32Add);
672    function.instruction(&Instruction::I32Load8U(MemArg {
673        offset: 0,
674        align: 0,
675        memory_index: 0,
676    }));
677}
678
679fn store_byte(function: &mut Function, pointer: u32, offset: u32, value: u8) {
680    function.instruction(&Instruction::LocalGet(pointer));
681    function.instruction(&Instruction::I32Const(offset as i32));
682    function.instruction(&Instruction::I32Add);
683    function.instruction(&Instruction::I32Const(i32::from(value)));
684    function.instruction(&Instruction::I32Store8(MemArg {
685        offset: 0,
686        align: 0,
687        memory_index: 0,
688    }));
689}
690
691fn store_byte_global(function: &mut Function, pointer: u32, offset: u32, global: u32) {
692    function.instruction(&Instruction::LocalGet(pointer));
693    function.instruction(&Instruction::I32Const(offset as i32));
694    function.instruction(&Instruction::I32Add);
695    function.instruction(&Instruction::GlobalGet(global));
696    function.instruction(&Instruction::I32Store8(MemArg {
697        offset: 0,
698        align: 0,
699        memory_index: 0,
700    }));
701}
702
703fn store_i64_constant(function: &mut Function, pointer: u32, offset: u32, value: i64) {
704    function.instruction(&Instruction::I64Const(value));
705    function.instruction(&Instruction::LocalSet(3));
706    for byte in 0..8 {
707        store_i64_byte_from_local(function, pointer, 3, offset + byte, 7 - byte);
708    }
709}
710
711fn store_i64_byte_from_local(
712    function: &mut Function,
713    pointer: u32,
714    local: u32,
715    offset: u32,
716    shift_bytes: u32,
717) {
718    function.instruction(&Instruction::LocalGet(pointer));
719    function.instruction(&Instruction::I32Const(offset as i32));
720    function.instruction(&Instruction::I32Add);
721    function.instruction(&Instruction::LocalGet(local));
722    function.instruction(&Instruction::I64Const(i64::from(shift_bytes * 8)));
723    function.instruction(&Instruction::I64ShrU);
724    function.instruction(&Instruction::I32WrapI64);
725    function.instruction(&Instruction::I32Store8(MemArg {
726        offset: 0,
727        align: 0,
728        memory_index: 0,
729    }));
730}
731
732fn adapter_manifest(
733    interface: &WasmInterface,
734    module_digest: &str,
735    interface_digest: &str,
736    adapter_digest: &str,
737    exports: &[BindingFunction],
738) -> String {
739    let exports = exports
740        .iter()
741        .map(|export| {
742            Form::Map(vec![
743                (keyword("hara/name"), symbol(&export.name)),
744                (keyword("wasm/export"), string(&export.wasm_export)),
745            ])
746        })
747        .collect();
748    Form::Map(vec![
749        (keyword("schema"), string(ADAPTER_MANIFEST_SCHEMA)),
750        (keyword("target"), keyword(ADAPTER_TARGET)),
751        (keyword("namespace"), symbol(&interface.namespace)),
752        (
753            keyword("composition"),
754            Form::Map(vec![
755                (keyword("import-module"), string(LIBRARY_IMPORT_MODULE)),
756                (keyword("library"), string(&interface.module)),
757            ]),
758        ),
759        (
760            keyword("inputs"),
761            Form::Map(vec![
762                (keyword("module-digest"), string(module_digest)),
763                (keyword("interface-digest"), string(interface_digest)),
764            ]),
765        ),
766        (keyword("adapter-digest"), string(adapter_digest)),
767        (
768            keyword("tool"),
769            Form::Map(vec![
770                (keyword("name"), string("hara-wasm-bindgen")),
771                (keyword("version"), string(env!("CARGO_PKG_VERSION"))),
772            ]),
773        ),
774        (keyword("exports"), Form::Vector(exports)),
775    ])
776    .to_string()
777}
778
779fn hta_adapter_manifest(
780    interface: &WasmInterface,
781    module_digest: &str,
782    interface_digest: &str,
783    adapter_digest: &str,
784    exports: &[BindingFunction],
785) -> String {
786    let exports = exports
787        .iter()
788        .map(|export| {
789            let mut fields = vec![
790                (keyword("hara/name"), symbol(&export.name)),
791                (keyword("wasm/export"), string(&export.wasm_export)),
792                (keyword("async"), Form::Bool(true)),
793            ];
794            if let Some(operation) = export.operation.as_deref() {
795                fields.push((keyword("operation"), string(operation)));
796            }
797            Form::Map(fields)
798        })
799        .collect();
800    Form::Map(vec![
801        (keyword("schema"), string(ADAPTER_MANIFEST_SCHEMA)),
802        (keyword("target"), keyword(HTA_ADAPTER_TARGET)),
803        (keyword("namespace"), symbol(&interface.namespace)),
804        (
805            keyword("composition"),
806            Form::Map(vec![
807                (keyword("import-module"), string(LIBRARY_IMPORT_MODULE)),
808                (keyword("library"), string(&interface.module)),
809            ]),
810        ),
811        (
812            keyword("inputs"),
813            Form::Map(vec![
814                (keyword("module-digest"), string(module_digest)),
815                (keyword("interface-digest"), string(interface_digest)),
816                (keyword("ir-digest"), string(interface_digest)),
817            ]),
818        ),
819        (keyword("adapter-digest"), string(adapter_digest)),
820        (
821            keyword("tool"),
822            Form::Map(vec![
823                (keyword("name"), string("hara-wasm-bindgen")),
824                (keyword("version"), string(env!("CARGO_PKG_VERSION"))),
825                (keyword("digest"), string(&tool_digest())),
826            ]),
827        ),
828        (keyword("exports"), Form::Vector(exports)),
829    ])
830    .to_string()
831}
832
833fn result_types(value: WasmValueType) -> Vec<ValType> {
834    match value {
835        WasmValueType::Void => Vec::new(),
836        value => vec![val_type(value)],
837    }
838}
839
840fn val_type(value: WasmValueType) -> ValType {
841    match value {
842        WasmValueType::I32 => ValType::I32,
843        WasmValueType::I64 => ValType::I64,
844        WasmValueType::F32 => ValType::F32,
845        WasmValueType::F64 => ValType::F64,
846        WasmValueType::Void => panic!("void is not a parameter type"),
847    }
848}
849
850fn digest(bytes: &[u8]) -> String {
851    format!("sha256:{:x}", Sha256::digest(bytes))
852}
853
854fn tool_digest() -> String {
855    digest(format!("hara-wasm-bindgen@{}", env!("CARGO_PKG_VERSION")).as_bytes())
856}
857
858fn keyword(value: &str) -> Form {
859    Form::Keyword(value.to_owned())
860}
861
862fn symbol(value: &str) -> Form {
863    Form::Symbol(value.to_owned())
864}
865
866fn string(value: &str) -> Form {
867    Form::String(value.to_owned())
868}
869
870#[cfg(test)]
871mod tests {
872    use super::*;
873    use crate::wasm_binding::inspect_direct;
874
875    const ADD: &[u8] =
876        b"\0asm\x01\0\0\0\x01\x07\x01\x60\x02\x7e\x7e\x01\x7e\x03\x02\x01\0\x07\x07\x01\x03add\0\0\x0a\x09\x01\x07\0\x20\0\x20\x01\x7c\x0b";
877    const START: &[u8] = b"\0asm\x01\0\0\0\x08\x01\0";
878
879    fn interface() -> WasmInterface {
880        WasmInterface::parse(
881            r#"
882            (wasm/interface
883             {:schema "hara.wasm-interface/0-alpha"
884              :namespace math.scalar
885              :module "math.wasm"
886              :exports
887              {sum {:wasm/export "add"
888                    :arguments [{:name left :hara/type :i64 :wasm/type :i64}
889                                {:name right :hara/type :i64 :wasm/type :i64}]
890                    :returns {:hara/type :i64 :wasm/type :i64}}}})
891            "#,
892            "fixture",
893        )
894        .unwrap()
895    }
896
897    fn async_interface() -> WasmInterface {
898        WasmInterface::parse(
899            r#"
900            (wasm/interface
901             {:schema "hara.wasm-interface/0-alpha"
902              :namespace math.scalar
903              :module "math.wasm"
904              :exports
905              {sum {:wasm/export "add"
906                    :async true
907                    :arguments [{:name left :hara/type :i64 :wasm/type :i64}
908                                {:name right :hara/type :i64 :wasm/type :i64}]
909                    :returns {:hara/type :i64 :wasm/type :i64}}}})
910            "#,
911            "fixture",
912        )
913        .unwrap()
914    }
915
916    fn multi_interface() -> WasmInterface {
917        WasmInterface::parse(
918            r#"
919            (wasm/interface
920             {:schema "hara.wasm-interface/0-alpha"
921              :namespace math.scalar
922              :module "math.wasm"
923              :exports
924              {difference {:wasm/export "sub"
925                           :arguments [{:name left :hara/type :i64 :wasm/type :i64}
926                                       {:name right :hara/type :i64 :wasm/type :i64}]
927                           :returns {:hara/type :i64 :wasm/type :i64}}
928               sum {:wasm/export "add"
929                    :arguments [{:name left :hara/type :i64 :wasm/type :i64}
930                                {:name right :hara/type :i64 :wasm/type :i64}]
931                    :returns {:hara/type :i64 :wasm/type :i64}}}})
932            "#,
933            "fixture",
934        )
935        .unwrap()
936    }
937
938    fn multi_library() -> Vec<u8> {
939        let mut module = Module::new();
940        let mut types = TypeSection::new();
941        types.function([ValType::I64, ValType::I64], [ValType::I64]);
942        module.section(&types);
943
944        let mut functions = FunctionSection::new();
945        functions.function(0);
946        functions.function(0);
947        module.section(&functions);
948
949        let mut exports = ExportSection::new();
950        exports.export("add", ExportKind::Func, 0);
951        exports.export("sub", ExportKind::Func, 1);
952        module.section(&exports);
953
954        let mut code = wasm_encoder::CodeSection::new();
955        for instruction in [Instruction::I64Add, Instruction::I64Sub] {
956            let mut function = Function::new([]);
957            function.instruction(&Instruction::LocalGet(0));
958            function.instruction(&Instruction::LocalGet(1));
959            function.instruction(&instruction);
960            function.instruction(&Instruction::End);
961            code.function(&function);
962        }
963        module.section(&code);
964        module.finish()
965    }
966
967    #[test]
968    fn adapter_is_deterministic_and_records_all_input_digests() {
969        let interface = interface();
970        let first = generate_adapter(ADD, &interface).unwrap();
971        let second = generate_adapter(ADD, &interface).unwrap();
972        assert_eq!(first, second);
973        assert!(first.manifest.contains("hara.wasm-adapter/0-alpha"));
974        assert!(first.manifest.contains(":module-digest"));
975        assert!(first.manifest.contains(":interface-digest"));
976        assert!(first.manifest.contains(":adapter-digest"));
977    }
978
979    #[test]
980    fn adapter_exports_hara_names_and_imports_exact_library_names() {
981        let artifact = generate_adapter(ADD, &interface()).unwrap();
982        let inspection = inspect_direct(&artifact.bytes).unwrap();
983        assert_eq!(inspection.imports[0].module, "hara/library");
984        assert_eq!(inspection.imports[0].name, "add");
985        assert_eq!(inspection.exports[0].name, "sum");
986        assert_eq!(
987            inspection.exports[0].signature.arguments,
988            vec!["i64", "i64"]
989        );
990        assert_eq!(inspection.exports[0].signature.returns, "i64");
991    }
992
993    #[test]
994    fn adapter_forwards_calls_when_composed_with_the_wrapped_library() {
995        let artifact = generate_adapter(ADD, &interface()).unwrap();
996        let engine = wasmtime::Engine::default();
997        let library = wasmtime::Module::new(&engine, ADD).unwrap();
998        let adapter = wasmtime::Module::new(&engine, &artifact.bytes).unwrap();
999        let mut store = wasmtime::Store::new(&engine, ());
1000        let library_instance = wasmtime::Instance::new(&mut store, &library, &[]).unwrap();
1001        let add = library_instance.get_func(&mut store, "add").unwrap();
1002        let adapter_instance =
1003            wasmtime::Instance::new(&mut store, &adapter, &[add.into()]).unwrap();
1004        let sum = adapter_instance
1005            .get_typed_func::<(i64, i64), i64>(&mut store, "sum")
1006            .unwrap();
1007
1008        assert_eq!(sum.call(&mut store, (19, 23)).unwrap(), 42);
1009    }
1010
1011    #[test]
1012    fn multi_export_adapter_forwards_each_import_with_its_declared_signature() {
1013        let library_bytes = multi_library();
1014        let artifact = generate_adapter(&library_bytes, &multi_interface()).unwrap();
1015        let engine = wasmtime::Engine::default();
1016        let library = wasmtime::Module::new(&engine, &library_bytes).unwrap();
1017        let adapter = wasmtime::Module::new(&engine, &artifact.bytes).unwrap();
1018        let mut store = wasmtime::Store::new(&engine, ());
1019        let library_instance = wasmtime::Instance::new(&mut store, &library, &[]).unwrap();
1020        let add = library_instance.get_func(&mut store, "add").unwrap();
1021        let sub = library_instance.get_func(&mut store, "sub").unwrap();
1022        let adapter_instance =
1023            wasmtime::Instance::new(&mut store, &adapter, &[sub.into(), add.into()]).unwrap();
1024        let difference = adapter_instance
1025            .get_typed_func::<(i64, i64), i64>(&mut store, "difference")
1026            .unwrap();
1027        let sum = adapter_instance
1028            .get_typed_func::<(i64, i64), i64>(&mut store, "sum")
1029            .unwrap();
1030
1031        assert_eq!(difference.call(&mut store, (23, 19)).unwrap(), 4);
1032        assert_eq!(sum.call(&mut store, (19, 23)).unwrap(), 42);
1033    }
1034
1035    #[test]
1036    fn adapter_output_order_is_canonical_for_constructed_interfaces() {
1037        let mut interface = multi_interface();
1038        interface.exports.reverse();
1039        let first = generate_adapter(&multi_library(), &interface).unwrap();
1040        interface.exports.reverse();
1041        let second = generate_adapter(&multi_library(), &interface).unwrap();
1042
1043        assert_eq!(first.bytes, second.bytes);
1044    }
1045
1046    #[test]
1047    fn malformed_or_richer_interfaces_are_rejected_before_generation() {
1048        let interface = WasmInterface::parse(
1049            r#"
1050            {:schema "hara.wasm-interface/0-alpha"
1051             :namespace codec.echo
1052             :module "echo.wasm"
1053             :memory {:export "memory" :allocate "alloc"}
1054             :exports
1055             {echo {:wasm/export "echo"
1056                    :arguments [{:name input :hara/type :bytes :wasm/type :i32
1057                                 :lower [:pointer :length] :ownership :borrowed}]
1058                    :returns {:hara/type :bytes :wasm/type :i64
1059                              :lift :packed-i64 :ownership :callee}}}}
1060            "#,
1061            "fixture",
1062        )
1063        .unwrap();
1064        let error = generate_adapter(ADD, &interface).unwrap_err();
1065        assert!(error.contains("memory requires"));
1066    }
1067
1068    #[test]
1069    fn start_functions_are_rejected_during_static_validation() {
1070        let error = generate_adapter(START, &interface()).unwrap_err();
1071        assert!(error.starts_with("wasm-adapter/start-denied"));
1072        let error = generate_hta_adapter(START, &async_interface()).unwrap_err();
1073        assert!(error.starts_with("wasm-adapter/start-denied"));
1074    }
1075
1076    #[test]
1077    fn hta_adapter_dispatches_a_scalar_request_and_emits_a_terminal_event() {
1078        let artifact = generate_hta_adapter(ADD, &async_interface()).unwrap();
1079        let engine = wasmtime::Engine::default();
1080        let library = wasmtime::Module::new(&engine, ADD).unwrap();
1081        let adapter = wasmtime::Module::new(&engine, &artifact.bytes).unwrap();
1082        let mut store = wasmtime::Store::new(&engine, ());
1083        let library_instance = wasmtime::Instance::new(&mut store, &library, &[]).unwrap();
1084        let add = library_instance.get_func(&mut store, "add").unwrap();
1085        let adapter_instance =
1086            wasmtime::Instance::new(&mut store, &adapter, &[add.into()]).unwrap();
1087        let memory = adapter_instance.get_memory(&mut store, "memory").unwrap();
1088        let alloc = adapter_instance
1089            .get_typed_func::<i32, i32>(&mut store, "hta_alloc")
1090            .unwrap();
1091        let start = adapter_instance
1092            .get_typed_func::<(i32, i32), i64>(&mut store, "hta_start")
1093            .unwrap();
1094        let next_event = adapter_instance
1095            .get_typed_func::<(), i64>(&mut store, "hta_next_event")
1096            .unwrap();
1097        let request = crate::hta::encode(&crate::core::Value::Vector(
1098            vec![
1099                crate::core::Value::String("sum".into()),
1100                crate::core::Value::Vector(
1101                    vec![
1102                        crate::core::Value::Number(19),
1103                        crate::core::Value::Number(23),
1104                    ]
1105                    .into(),
1106                ),
1107            ]
1108            .into(),
1109        ))
1110        .unwrap();
1111        let pointer = alloc.call(&mut store, request.len() as i32).unwrap();
1112        memory
1113            .write(&mut store, pointer as usize, &request)
1114            .unwrap();
1115
1116        let task = start
1117            .call(&mut store, (pointer, request.len() as i32))
1118            .unwrap();
1119        assert_eq!(task, 1);
1120        let packed = next_event.call(&mut store, ()).unwrap() as u64;
1121        let event_pointer = (packed >> 32) as usize;
1122        let event_size = (packed & u64::from(u32::MAX)) as usize;
1123        let mut event = vec![0; event_size];
1124        memory.read(&store, event_pointer, &mut event).unwrap();
1125        assert_eq!(
1126            crate::hta::decode_canonical(&event).unwrap(),
1127            crate::core::Value::Vector(
1128                vec![
1129                    crate::core::Value::Number(0),
1130                    crate::core::Value::Number(1),
1131                    crate::core::Value::Number(42),
1132                ]
1133                .into()
1134            )
1135        );
1136        assert_eq!(next_event.call(&mut store, ()).unwrap(), 0);
1137    }
1138}