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#[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
35pub 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
76pub 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}