//! The WIT file (extensions/quantum-wasmq.wit) is the interface the modules have: each //! world's functions lower, by the Component Model's canonical ABI, to exactly the core //! types the reference modules export and an `extern` of the same shape lowers to (§5.2), //! and its fault reasons are the ones a receipt names. use wai_quantum::qasm::{ExternArg, ExternDecl}; use wai_wasmq::{abi, profile}; use wit_parser::abi::{AbiVariant, WasmType}; use wit_parser::{Resolve, WorldItem, WorldKey}; const WIT: &str = include_str!("../../extensions/quantum-wasmq.wit"); const DECODER: &[u8] = include_bytes!("../../quantum-conformance/wasmq/modules/qldpc_decoder.wasm"); const PLANNER: &[u8] = include_bytes!("../../quantum-conformance/wasmq/modules/atom_planner.wasm"); fn val(t: WasmType) -> wasmparser::ValType { match t { WasmType::I32 | WasmType::Pointer | WasmType::Length => wasmparser::ValType::I32, WasmType::I64 | WasmType::PointerOrI64 => wasmparser::ValType::I64, WasmType::F32 => wasmparser::ValType::F32, WasmType::F64 => wasmparser::ValType::F64, } } /// A core function type: its parameters and results. type Core = (Vec, Vec); fn exports(resolve: &Resolve, world: &str) -> Vec<(String, Core)> { let pkg = resolve.packages.iter().next().unwrap().0; let w = resolve.select_world(&[pkg], Some(world)).unwrap(); let mut out = Vec::new(); for (k, item) in &resolve.worlds[w].exports { if let (WorldKey::Name(name), WorldItem::Function(f)) = (k, item) { let s = resolve.wasm_signature(AbiVariant::GuestExport, f); out.push((name.clone(), (s.params.into_iter().map(val).collect(), s.results.into_iter().map(val).collect()))); } } out } fn resolve() -> Resolve { let mut r = Resolve::default(); r.push_str("quantum-wasmq.wit", WIT).unwrap(); r } #[test] fn the_reference_modules_export_what_their_worlds_lower_to() { let r = resolve(); for (world, module) in [("qldpc-decoder", DECODER), ("atom-planner", PLANNER)] { let facts = profile::check(module).unwrap(); let ex = exports(&r, world); assert!(ex.len() >= 3, "{world}"); for (name, sig) in ex { if name == "init" { continue; // optional, and neither reference module needs it } assert_eq!(facts.functions.get(&name), Some(&sig), "{world}: {name}"); } } } #[test] fn an_extern_lowers_as_its_wit_function_does() { let r = resolve(); let ex: std::collections::BTreeMap<_, _> = exports(&r, "qldpc-decoder").into_iter().collect(); let scalar = ExternDecl { name: "decode64".into(), args: vec![ExternArg::Bit(8)], ret: Some(13) }; assert_eq!(abi::core_type(&scalar), ex["decode64"]); let list = ExternDecl { name: "decode".into(), args: vec![ExternArg::Bit(72)], ret: Some(144) }; assert_eq!(abi::core_type(&list), ex["decode"]); // The lifecycle the runner calls is the one the module world declares. let m: std::collections::BTreeMap<_, _> = exports(&r, "module").into_iter().collect(); assert_eq!(m["wasmq-prior"].0.len(), 3); assert_eq!(m["wasmq-seed"], (vec![wasmparser::ValType::I64], vec![])); } #[test] fn an_angle_lowers_as_its_wit_u64_does() { // The second version's angle argument (§11.1): one u64, in the scalar form and the list. let r = resolve(); let ex: std::collections::BTreeMap<_, _> = exports(&r, "angle-callout").into_iter().collect(); let scalar = ExternDecl { name: "f".into(), args: vec![ExternArg::Angle(8), ExternArg::Uint(8)], ret: Some(16) }; assert_eq!(abi::core_type(&scalar), ex["f"]); let list = ExternDecl { name: "f".into(), args: vec![ExternArg::Angle(64), ExternArg::Bit(72)], ret: Some(8) }; assert_eq!(abi::core_type(&list), (vec![wasmparser::ValType::I64, wasmparser::ValType::I32, wasmparser::ValType::I32], vec![wasmparser::ValType::I32])); } #[test] fn the_fault_reasons_are_the_ones_a_receipt_names() { let r = resolve(); let iface = r.interfaces.iter().find(|(_, i)| i.name.as_deref() == Some("receipt")).unwrap().1; let ty = iface.types["fault-reason"]; let wit_parser::TypeDefKind::Enum(e) = &r.types[ty].kind else { panic!("fault-reason is an enum") }; let wit: Vec = e.cases.iter().map(|c| c.name.replace('-', "_")).collect(); use wai_wasmq::engine::Trap::*; let mut ours: Vec = [Unreachable, MemoryOutOfBounds, TableOutOfBounds, IndirectCallNull, IndirectCallType, DivideByZero, IntegerOverflow, InvalidConversion, Fuel, Stack].iter().map(|t| t.reason().to_string()).collect(); ours.push("bad_result".into()); assert_eq!(wit, ours); }