diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 6f7a6ceef42..3726da497fb 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -40,6 +40,7 @@ tys! { RESULT UNIT CLAMPED + FIXED_ARRAY } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -72,6 +73,7 @@ pub enum Descriptor { Option(Box), Result(Box), Unit, + FixedArray(Box, u32), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -161,6 +163,9 @@ impl Descriptor { CHAR => Descriptor::Char, UNIT => Descriptor::Unit, CLAMPED => Descriptor::_decode(data, true), + FIXED_ARRAY => { + Descriptor::FixedArray(Box::new(Descriptor::_decode(data, clamped)), get(data)) + } other => panic!("unknown descriptor: {}", other), } } @@ -193,6 +198,7 @@ impl Descriptor { Descriptor::Slice(ref d) => &**d, _ => return None, }, + Descriptor::FixedArray(ref d, _) => &**d, _ => return None, }; match *inner { diff --git a/crates/cli-support/src/descriptors.rs b/crates/cli-support/src/descriptors.rs index b4af4bc36a9..245d45ac619 100644 --- a/crates/cli-support/src/descriptors.rs +++ b/crates/cli-support/src/descriptors.rs @@ -47,6 +47,12 @@ impl WasmBindgenDescriptorsSection { interpreter: &mut Interpreter, ) -> Result<(), Error> { let mut to_remove = Vec::new(); + let to_print = module + .exports + .iter() + .map(|m| m.name.clone()) + .collect::>(); + println!("All module exports:\n{to_print:#?}"); for export in module.exports.iter() { let prefix = "__wbindgen_describe_"; if !export.name.starts_with(prefix) { @@ -59,7 +65,10 @@ impl WasmBindgenDescriptorsSection { if let Some(d) = interpreter.interpret_descriptor(id, module) { let name = &export.name[prefix.len()..]; let descriptor = Descriptor::decode(d); + println!("Adding descriptor: {name:?} - {descriptor:#?}"); self.descriptors.insert(name.to_string(), descriptor); + } else { + eprintln!("Somehow this returned none...?, id: {:?}", id); } to_remove.push(export.id()); } diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 83a04f92fb6..c73ae3b4e53 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -1101,6 +1101,42 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> js.push(format!("v{}", i)) } + Instruction::WasmToFixedArray { kind, length } => { + // Need to convert to JS num here + let convert_to_js_num = |val: &str| -> String { + match kind { + AdapterType::U32 => format!("{} >>> 0", val), + AdapterType::U64 => format!("BigInt.asUintN(64, {val})"), + _ => val.to_string(), + } + }; + let mut to_ret = (0..*length) + .map(|_| { + let val = js.pop(); + convert_to_js_num(&val) + }) + .collect::>(); + to_ret.reverse(); + js.push(format!("[{}]", to_ret.join(", "))); + } + + Instruction::FixedArrayToWasm { kind, length } => { + let input = js.pop(); + js.prelude(&format!("if ({input}.length !== {length}) {{\n throw new Error(`Expected an Array of length {length}, received array of length ${{{input}.length}}`);\n}}")); + // if matches!(kind, AdapterType::I64 | AdapterType::S64 | AdapterType::U64) { + // js.assert_bigint(&element); + // } else { + // js.assert_number(&element); + // } + // let elements = (0..*length) + // .map(|i| format!("{}[{}]", input, i)) + // .collect::>(); + for i in 0..*length { + let element = format!("{}[{}]", input, i); + js.push(element); + } + } + Instruction::OptionVectorLoad { kind, mem, free } => { let len = js.pop(); let ptr = js.pop(); @@ -1300,5 +1336,10 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String) { AdapterType::NamedExternref(name) => dst.push_str(name), AdapterType::Struct(name) => dst.push_str(name), AdapterType::Function => dst.push_str("any"), + AdapterType::Array(ty, _) => { + let mut inner = String::new(); + adapter2ts(ty, &mut inner); + dst.push_str(&format!("Array<{}>", inner)); + } } } diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index ee07fe86b28..9b5f0b18f75 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -439,6 +439,8 @@ impl Bindgen { let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?; cx.generate()?; let (js, ts, start) = cx.finalize(stem)?; + // let (js, ts, start) = (String::default(), String::default(), None); + let generated = Generated { snippets: aux.snippets.clone(), local_modules: aux.local_modules.clone(), diff --git a/crates/cli-support/src/multivalue.rs b/crates/cli-support/src/multivalue.rs index 61a20878e63..490dffc2343 100644 --- a/crates/cli-support/src/multivalue.rs +++ b/crates/cli-support/src/multivalue.rs @@ -1,17 +1,19 @@ -use crate::wit::{Adapter, NonstandardWitSection}; +use crate::wit::{Adapter, AdapterType, NonstandardWitSection}; use crate::wit::{AdapterKind, Instruction, WasmBindgenAux}; use anyhow::{anyhow, Error}; use walrus::ir::Value; use walrus::{FunctionId, InitExpr, Module}; use wasm_bindgen_multi_value_xform as multi_value_xform; +use wasm_bindgen_multi_value_xform::{ArrayLoad, ToTransform}; use wasm_bindgen_wasm_conventions as wasm_conventions; +use wasm_bindgen_wasm_conventions::get_func_name_from_id; pub fn run(module: &mut Module) -> Result<(), Error> { let mut adapters = module .customs .delete_typed::() .unwrap(); - let mut to_xform = Vec::new(); + let mut to_xform: Vec = Vec::new(); let mut slots = Vec::new(); for (_, adapter) in adapters.adapters.iter_mut() { @@ -56,12 +58,16 @@ enum Slot<'a> { fn extract_xform<'a>( module: &Module, adapter: &'a mut Adapter, - to_xform: &mut Vec<(walrus::FunctionId, usize, Vec)>, + to_xform: &mut Vec, slots: &mut Vec>, ) { + let adapter_clone = adapter.clone(); let instructions = match &mut adapter.kind { AdapterKind::Local { instructions } => instructions, - AdapterKind::Import { .. } => return, + AdapterKind::Import { .. } => { + println!("Import adapter: {adapter_clone:#?}"); + return; + } }; // If the first instruction is a `Retptr`, then this must be an exported @@ -77,6 +83,16 @@ fn extract_xform<'a>( } _ => true, }); + let array_config = instructions.iter().find_map(|i| match &i.instr { + Instruction::WasmToFixedArray { kind, length: _ } => match kind { + AdapterType::S8 => Some(ArrayLoad::I8), + AdapterType::S16 => Some(ArrayLoad::I16), + AdapterType::U8 => Some(ArrayLoad::U8), + AdapterType::U16 => Some(ArrayLoad::U16), + _ => None, + }, + _ => None, + }); let slot = instructions .iter_mut() .find_map(|i| match &mut i.instr { @@ -97,8 +113,37 @@ fn extract_xform<'a>( }, Slot::TableElement(func_index) => resolve_table_entry(module, *func_index), }; - to_xform.push((id, 0, types)); + println!( + "About to transform `{}`: {:#?}", + get_func_name_from_id(module, id), + adapter_clone + ); + to_xform.push((id, 0, types, array_config)); slots.push(slot); + } else { + println!("Adapter that we didn't xform: {:#?}", adapter_clone); + if let Some(slot) = instructions.iter_mut().find_map(|i| match &mut i.instr { + Instruction::CallCore(f) => Some(Slot::Id(f)), + Instruction::CallExport(e) => Some(Slot::Export(*e)), + Instruction::CallTableElement(index) => Some(Slot::TableElement(*index)), + _ => None, + }) { + let id = match &slot { + Slot::Id(i) => **i, + Slot::Export(e) => match module.exports.get(*e).item { + walrus::ExportItem::Function(f) => f, + _ => panic!("found call to non-function export"), + }, + Slot::TableElement(func_index) => resolve_table_entry(module, *func_index), + }; + + println!( + "Got name for adapter function {:?} with ID {:?}: {}", + adapter_clone.id, + id, + get_func_name_from_id(module, id) + ); + } } // If the last instruction is a `StoreRetptr`, then this must be an adapter diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index 483185ce627..800b8903855 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -11,6 +11,7 @@ use crate::descriptor::Descriptor; use crate::wit::InstructionData; use crate::wit::{AdapterType, Instruction, InstructionBuilder, StackChange}; use anyhow::{bail, format_err, Error}; +use std::ops::Deref; use walrus::ValType; impl InstructionBuilder<'_, '_> { @@ -146,6 +147,28 @@ impl InstructionBuilder<'_, '_> { // Largely synthetic and can't show up Descriptor::ClampedU8 => unreachable!(), + Descriptor::FixedArray(d,length) => { + let (input_ty, output_ty) = match d.deref() { + Descriptor::U8 => (AdapterType::U8, AdapterType::I32), + Descriptor::I8 => (AdapterType::S8, AdapterType::I32), + Descriptor::U16 => (AdapterType::U16, AdapterType::I32), + Descriptor::I16 => (AdapterType::S16, AdapterType::I32), + Descriptor::U32 => (AdapterType::U32, AdapterType::I32), + Descriptor::I32 => (AdapterType::S32, AdapterType::I32), + Descriptor::U64 => (AdapterType::U64, AdapterType::I64), + Descriptor::I64 => (AdapterType::S64, AdapterType::I64), + Descriptor::F32 => (AdapterType::F32, AdapterType::F32), + Descriptor::F64 => (AdapterType::F64, AdapterType::F64), + d => unimplemented!("unsupported type for fixed size arrays: {d:?}"), + }; + let input = AdapterType::Array(Box::new(input_ty.clone()), *length as usize); + let instr = Instruction::FixedArrayToWasm { + kind: input_ty, + length: *length as usize, + }; + let outputs = (0..*length).map(|_| output_ty.clone()).collect::>(); + self.instruction(&[input], instr, &outputs); + } } Ok(()) } diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index d90dcd3f5ee..3fa9f07d202 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -2,6 +2,7 @@ use crate::descriptor::Descriptor; use crate::wit::{AdapterType, Instruction, InstructionBuilder}; use crate::wit::{InstructionData, StackChange}; use anyhow::{bail, format_err, Error}; +use std::ops::Deref; use walrus::ValType; impl InstructionBuilder<'_, '_> { @@ -154,6 +155,32 @@ impl InstructionBuilder<'_, '_> { // nothing to do Descriptor::Unit => {} + Descriptor::FixedArray(d, length) => { + let (output_ty, input_ty) = match d.deref() { + Descriptor::U8 => (AdapterType::U8, AdapterType::I32), + Descriptor::I8 => (AdapterType::S8, AdapterType::I32), + Descriptor::U16 => (AdapterType::U16, AdapterType::I32), + Descriptor::I16 => (AdapterType::S16, AdapterType::I32), + Descriptor::U32 => (AdapterType::U32, AdapterType::I32), + Descriptor::I32 => (AdapterType::S32, AdapterType::I32), + Descriptor::U64 => (AdapterType::U64, AdapterType::I64), + Descriptor::I64 => (AdapterType::S64, AdapterType::I64), + Descriptor::F32 => (AdapterType::F32, AdapterType::F32), + Descriptor::F64 => (AdapterType::F64, AdapterType::F64), + d => unimplemented!("unsupported type for fixed size arrays: {d:?}",), + }; + let inputs = (0..*length).map(|_| input_ty.clone()).collect::>(); + let instr = Instruction::WasmToFixedArray { + kind: output_ty.clone(), + length: *length as usize, + }; + self.instruction( + &inputs, + instr, + &[AdapterType::Array(Box::new(output_ty), *length as usize)], + ); + } + // Largely synthetic and can't show up Descriptor::ClampedU8 => unreachable!(), } @@ -401,6 +428,11 @@ impl InstructionBuilder<'_, '_> { self.get(AdapterType::I32); self.get(AdapterType::I32); } + + Descriptor::FixedArray(_, _) => { + bail!("SoonTM") + } + Descriptor::String => { // fetch the ptr/length ... self.get(AdapterType::I32); diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 5a771d59675..d5bb58302de 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -87,6 +87,7 @@ pub enum AdapterType { Struct(String), NamedExternref(String), Function, + Array(Box, usize), } #[derive(Debug, Clone)] @@ -277,6 +278,14 @@ pub enum Instruction { mem: walrus::MemoryId, free: walrus::FunctionId, }, + FixedArrayToWasm { + kind: AdapterType, + length: usize, + }, + WasmToFixedArray { + kind: AdapterType, + length: usize, + }, /// pops i32, loads externref from externref table TableGet, /// pops two i32 data pointers, pushes an externref closure diff --git a/crates/multi-value-xform/src/lib.rs b/crates/multi-value-xform/src/lib.rs index 2bfb5bea374..24fba3f5bb9 100644 --- a/crates/multi-value-xform/src/lib.rs +++ b/crates/multi-value-xform/src/lib.rs @@ -92,6 +92,62 @@ #![deny(missing_docs, missing_debug_implementations)] +use walrus::ir::{ExtendedLoad, LoadKind}; + +/// The possible inner values of a fixed-size array which have one or two byte +/// alignments. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ArrayLoad { + /// I8 + I8, + /// U8 + U8, + /// I16 + I16, + /// U16 + U16, +} + +impl ArrayLoad { + fn offset(&self) -> u32 { + match self { + ArrayLoad::I8 | ArrayLoad::U8 => 1, + ArrayLoad::I16 | ArrayLoad::U16 => 2, + } + } + + fn load_kind(&self) -> LoadKind { + match self { + Self::I8 => LoadKind::I32_8 { + kind: ExtendedLoad::SignExtend, + }, + Self::U8 => LoadKind::I32_8 { + kind: ExtendedLoad::ZeroExtend, + }, + Self::I16 => LoadKind::I32_16 { + kind: ExtendedLoad::SignExtend, + }, + Self::U16 => LoadKind::I32_16 { + kind: ExtendedLoad::ZeroExtend, + }, + } + } +} + +/// A single instance of an exported function that we want to transform and +/// information required to transform it. The `usize` is the index of the +/// return pointer parameter that will be removed. The `Vec` +/// is the new result type that will be returned directly instead of via the +/// return pointer. The `Option` is used to indicate how to load +/// when dealing with fixed-size arrays with types that have one or two byte +/// alignments. +pub type ToTransform = ( + walrus::FunctionId, + usize, + Vec, + Option, +); + /// Run the transformation. /// /// See the module-level docs for details on the transformation. @@ -115,10 +171,10 @@ pub fn run( module: &mut walrus::Module, memory: walrus::MemoryId, shadow_stack_pointer: walrus::GlobalId, - to_xform: &[(walrus::FunctionId, usize, Vec)], + to_xform: &[ToTransform], ) -> Result, anyhow::Error> { let mut wrappers = Vec::new(); - for (func, return_pointer_index, results) in to_xform { + for (func, return_pointer_index, results, array_config) in to_xform { wrappers.push(xform_one( module, memory, @@ -126,6 +182,7 @@ pub fn run( *func, *return_pointer_index, results, + array_config, )?); } Ok(wrappers) @@ -144,6 +201,7 @@ fn xform_one( func: walrus::FunctionId, return_pointer_index: usize, results: &[walrus::ValType], + array_config: &Option, ) -> Result { if module.globals.get(shadow_stack_pointer).ty != walrus::ValType::I32 { anyhow::bail!("shadow stack pointer global does not have type `i32`"); @@ -240,13 +298,31 @@ fn xform_one( body.local_get(return_pointer); match ty { walrus::ValType::I32 => { - debug_assert_eq!(offset % 4, 0); - body.load( - memory, - walrus::ir::LoadKind::I32 { atomic: false }, - walrus::ir::MemArg { align: 4, offset }, - ); - offset += 4; + if let Some(array_config) = array_config { + let align = array_config.offset(); + body.load( + memory, + array_config.load_kind(), + walrus::ir::MemArg { align, offset }, + ); + offset += align; + } else { + debug_assert_eq!(offset % 4, 0); + body.load( + memory, + walrus::ir::LoadKind::I32 { atomic: false }, + walrus::ir::MemArg { align: 4, offset }, + ); + offset += 4; + } + // body.load( + // memory, + // walrus::ir::LoadKind::I32_16 { + // kind: ExtendedLoad::ZeroExtend, + // }, + // walrus::ir::MemArg { align: 2, offset }, + // ); + // offset += 2; } walrus::ValType::I64 => { offset = round_up_to_alignment(offset, 8); diff --git a/crates/wasm-conventions/src/lib.rs b/crates/wasm-conventions/src/lib.rs index d906e0dec63..c0f4c1ce375 100755 --- a/crates/wasm-conventions/src/lib.rs +++ b/crates/wasm-conventions/src/lib.rs @@ -12,6 +12,11 @@ use walrus::{ InitExpr, MemoryId, Module, ValType, }; +/// Utility method to get a function name inside a module with a function ID +pub fn get_func_name_from_id(module: &Module, function_id: FunctionId) -> String { + module.funcs.get(function_id).name.clone().unwrap() +} + /// Get a Wasm module's canonical linear memory. pub fn get_memory(module: &Module) -> Result { let mut memories = module.memories.iter().map(|m| m.id()); diff --git a/src/convert/impls.rs b/src/convert/impls.rs index e8636fe9e66..b1a71c2dec5 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -386,3 +386,34 @@ impl IntoWasmAbi for JsError { self.value.into_abi() } } + +macro_rules! wasm_abi_array { + ($($t:ty)*) => ($( + unsafe impl WasmAbi for [$t; LEN] {} + )*) +} + +macro_rules! from_into_wasm_abi_array { + ($($t:ty)*) => ($( + wasm_abi_array!($t); + + impl IntoWasmAbi for [$t; LEN] { + type Abi = [$t; LEN]; + + #[inline] + fn into_abi(self) -> Self::Abi { + self as [$t; LEN] + } + } + impl FromWasmAbi for [$t; LEN] { + type Abi = [$t; LEN]; + + #[inline] + unsafe fn from_abi(js: Self::Abi) -> [$t; LEN] { + js as [$t; LEN] + } + } + )*) +} + +from_into_wasm_abi_array!(u8 u16 u32 u64 i8 i16 i32 i64 f32 f64); diff --git a/src/describe.rs b/src/describe.rs index be149d7a90b..4a642935b75 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -46,6 +46,7 @@ tys! { RESULT UNIT CLAMPED + FIXED_ARRAY } #[inline(always)] // see the wasm-interpreter crate @@ -191,3 +192,11 @@ impl WasmDescribe for JsError { JsValue::describe(); } } + +impl WasmDescribe for [T; LEN] { + fn describe() { + inform(FIXED_ARRAY); + T::describe(); + inform(LEN as u32); + } +}