diff --git a/.cargo/config b/.cargo/config index 8ebcc839710..7e44fc809d4 100644 --- a/.cargo/config +++ b/.cargo/config @@ -2,3 +2,6 @@ # that. [target.wasm32-unknown-unknown] runner = 'cargo run -p wasm-bindgen-cli --bin wasm-bindgen-test-runner --' + +[target.wasm32-wasi] +runner = 'cargo run -p wasm-bindgen-cli --bin wasm-bindgen-test-runner --' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cf560554715..c70e06702b0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,6 +71,40 @@ jobs: WASM_BINDGEN_EXTERNREF: 1 NODE_ARGS: --experimental-wasm-reftypes + test_wasi_abi: + name: "Run wasm-bindgen crate tests with --wasi-abi (unix)" + runs-on: ubuntu-latest + env: + WASM_BINDGEN_SPLIT_LINKED_MODULES: 1 + TEST_WASI_ABI: 1 + steps: + - uses: actions/checkout@v3 + - run: rustup update --no-self-update stable && rustup default stable + - run: rustup target add wasm32-wasi + - uses: actions/setup-node@v3 + with: + node-version: '16' + - uses: ./.github/actions/setup-geckodriver + - run: cargo test --target wasm32-wasi + - run: cargo test --target wasm32-wasi --features serde-serialize + - run: cargo test --target wasm32-wasi --features enable-interning + - run: cargo test --target wasm32-wasi -p no-std + - run: cargo test --target wasm32-wasi -p wasm-bindgen-futures + - run: cargo test --target wasm32-wasi --test wasm + env: + WASM_BINDGEN_WEAKREF: 1 + - run: cargo test --target wasm32-wasi --test wasm + env: + WASM_BINDGEN_WEAKREF: 1 + WASM_BINDGEN_NO_DEBUG: 1 + - run: cargo test --target wasm32-wasi --test wasm --features serde-serialize + env: + WASM_BINDGEN_WEAKREF: 1 + - run: cargo test --target wasm32-wasi + env: + WASM_BINDGEN_EXTERNREF: 1 + NODE_ARGS: --experimental-wasm-reftypes + test_threads: name: "Run wasm-bindgen crate tests with multithreading enabled" runs-on: ubuntu-latest diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index e8874d5e055..3c66af16a47 100644 --- a/crates/cli-support/src/intrinsic.rs +++ b/crates/cli-support/src/intrinsic.rs @@ -10,7 +10,7 @@ use crate::descriptor::{self, Descriptor, Function}; macro_rules! intrinsics { - (pub enum Intrinsic { + (pub enum $enum_name:ident { $( #[symbol = $sym:tt] #[signature = fn($($arg:expr),*) -> $ret:expr] @@ -20,16 +20,16 @@ macro_rules! intrinsics { /// All wasm-bindgen intrinsics that could be depended on by a wasm /// module. #[derive(Debug)] - pub enum Intrinsic { + pub enum $enum_name { $($name,)* } - impl Intrinsic { + impl $enum_name { /// Returns the corresponding intrinsic for a symbol name, if one /// matches. - pub fn from_symbol(symbol: &str) -> Option { + pub fn from_symbol(symbol: &str) -> Option { match symbol { - $($sym => Some(Intrinsic::$name),)* + $($sym => Some(Self::$name),)* _ => None, } } @@ -40,7 +40,7 @@ macro_rules! intrinsics { use crate::descriptor::Descriptor::*; match self { $( - Intrinsic::$name => { + Self::$name => { descriptor::Function { shim_idx: 0, arguments: vec![$($arg),*], @@ -56,7 +56,7 @@ macro_rules! intrinsics { pub fn name(&self) -> &'static str { match self { $( - Intrinsic::$name => $sym, + Self::$name => $sym, )* } } @@ -278,3 +278,65 @@ intrinsics! { InitExternrefTable, } } + +intrinsics! { + pub enum WasiIntrinsic { + #[symbol = "__wbindgen_wasi_clock_time_get"] + #[signature = fn(I32, I64, I32) -> I32] + ClockTimeGet, + #[symbol = "__wbindgen_wasi_fd_write"] + #[signature = fn(I32, I32, I32, I32) -> I32] + FdWrite, + #[symbol = "__wbindgen_wasi_fd_read"] + #[signature = fn(I32, I32, I32, I32) -> I32] + FdRead, + #[symbol = "__wbindgen_wasi_sched_yield"] + #[signature = fn() -> Unit] + SchedYield, + #[symbol = "__wbindgen_wasi_random_get"] + #[signature = fn(I32, I32) -> I32] + RandomGet, + #[symbol = "__wbindgen_wasi_environ_get"] + #[signature = fn(I32, I32) -> I32] + EnvironGet, + #[symbol = "__wbindgen_wasi_environ_sizes_get"] + #[signature = fn(I32, I32) -> I32] + EnvironSizesGet, + #[symbol = "__wbindgen_wasi_fd_close"] + #[signature = fn(I32) -> I32] + FdClose, + #[symbol = "__wbindgen_wasi_fd_fdstat_get"] + #[signature = fn(I32, I32) -> I32] + FdFdStatGet, + #[symbol = "__wbindgen_wasi_fd_filestat_get"] + #[signature = fn(I32, I32) -> I32] + FdFileStatGet, + #[symbol = "__wbindgen_wasi_path_filestat_get"] + #[signature = fn(I32, I32, I32, I32, I32) -> I32] + PathFileStatGet, + #[symbol = "__wbindgen_wasi_fd_fdstat_set_flags"] + #[signature = fn(I32, U16) -> I32] + FdFdStatSetFlags, + #[symbol = "__wbindgen_wasi_fd_prestat_get"] + #[signature = fn(I32, I32) -> I32] + FdPrestatGet, + #[symbol = "__wbindgen_wasi_fd_prestat_dir_name"] + #[signature = fn(I32, I32) -> I32] + FdPrestatDirName, + #[symbol = "__wbindgen_wasi_fd_seek"] + #[signature = fn(I32, I64, U8) -> I32] + FdSeek, + #[symbol = "__wbindgen_wasi_path_open"] + #[signature = fn(I32, I32, I32, I32, I32, I64, I64, I32, I32) -> I32] + PathOpen, + #[symbol = "__wbindgen_wasi_proc_exit"] + #[signature = fn(I32) -> Unit] + ProcExit, + #[symbol = "__wbindgen_wasi_args_get"] + #[signature = fn(I32, I32) -> I32] + ArgsGet, + #[symbol = "__wbindgen_wasi_args_sizes_get"] + #[signature = fn(I32, I32) -> I32] + ArgsSizesGet, + } +} diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 0723928ee9f..416a70e65ec 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -633,6 +633,124 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> js.string_to_memory(*mem, *malloc, *realloc)?; } + Instruction::PackSlice(mem) => { + js.cx.inject_stack_pointer_shim()?; + + let len = js.pop(); + let ptr = js.pop(); + + let i = js.tmp(); + let mem = js.cx.expose_uint32_memory(*mem); + + js.prelude(&format!( + " + const argPtr{i} = wasm.__wbindgen_add_to_stack_pointer(-16); + {mem}()[argPtr{i} / 4] = {ptr}; + {mem}()[argPtr{i} / 4 + 1] = {len}; + " + )); + + js.push(format!("argPtr{i}")); + + js.finally(&format!("wasm.__wbindgen_add_to_stack_pointer(16);")); + } + Instruction::PackMutSlice(mem) => { + js.cx.inject_stack_pointer_shim()?; + + let idx = js.pop(); + let len = js.pop(); + let ptr = js.pop(); + + let i = js.tmp(); + let mem = js.cx.expose_uint32_memory(*mem); + + js.prelude(&format!( + " + const argPtr{i} = wasm.__wbindgen_add_to_stack_pointer(-16); + {mem}()[argPtr{i} / 4] = {ptr}; + {mem}()[argPtr{i} / 4 + 1] = {len}; + {mem}()[argPtr{i} / 4 + 2] = {idx}; + " + )); + + js.push(format!("argPtr{i}")); + + js.finally(&format!("wasm.__wbindgen_add_to_stack_pointer(16);")); + } + Instruction::UnpackSlice(mem) => { + let mem = js.cx.expose_uint32_memory(*mem); + + let arg_ptr = js.pop(); + let i = js.tmp(); + + js.prelude(&format!( + " + var ptr{i} = {mem}()[{arg_ptr} / 4]; + var len{i} = {mem}()[{arg_ptr} / 4 + 1]; + " + )); + + js.push(format!("ptr{i}")); + js.push(format!("len{i}")); + } + + Instruction::PackOption(mem, ty) => { + js.cx.inject_stack_pointer_shim()?; + + let content = js.pop(); + let discriminant = js.pop(); + + let i = js.tmp(); + let mem32 = js.cx.expose_uint32_memory(*mem); + + let (content_mem, content_size) = match ty { + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::U32 => (js.cx.expose_uint32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::I64 => (js.cx.expose_int64_memory(*mem), 8), + AdapterType::U64 => (js.cx.expose_uint64_memory(*mem), 8), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), + _ => bail!("Unexpected type passed to PackOption"), + }; + + js.prelude(&format!( + " + const argPtr{i} = wasm.__wbindgen_add_to_stack_pointer(-16); + {mem32}()[argPtr{i} / 4] = {discriminant}; + {content_mem}()[argPtr{i} / {content_size} + 1] = {content}; + " + )); + + js.push(format!("argPtr{i}")); + + js.finally(&format!("wasm.__wbindgen_add_to_stack_pointer(16);")); + } + Instruction::UnpackOption(mem, ty) => { + let mem32 = js.cx.expose_uint32_memory(*mem); + let (content_mem, content_size) = match ty { + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::U32 => (js.cx.expose_uint32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::I64 => (js.cx.expose_int64_memory(*mem), 8), + AdapterType::U64 => (js.cx.expose_uint64_memory(*mem), 8), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), + _ => bail!("Unexpected type passed to PackOption"), + }; + + let arg_ptr = js.pop(); + let i = js.tmp(); + + js.prelude(&format!( + " + var discriminant{i} = {mem32}()[{arg_ptr} / 4]; + var content{i} = {content_mem}()[{arg_ptr} / {content_size} + 1]; + " + )); + + js.push(format!("discriminant{i}")); + js.push(format!("content{i}")); + } + Instruction::Retptr { size } => { js.cx.inject_stack_pointer_shim()?; js.prelude(&format!( diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 1a1de40e909..0886fb9320a 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -17,6 +17,7 @@ use std::path::{Path, PathBuf}; use walrus::{FunctionId, ImportId, MemoryId, Module, TableId, ValType}; mod binding; +mod wasi_intrinsics; pub struct Context<'a> { globals: String, @@ -3147,6 +3148,12 @@ impl<'a> Context<'a> { self.invoke_intrinsic(intrinsic, args, prelude) } + AuxImport::WasiIntrinsic(intrinsic) => { + assert!(kind == AdapterJsImportKind::Normal); + assert!(!variadic); + self.invoke_wasi_intrinsic(intrinsic, args, prelude) + } + AuxImport::LinkTo(path, content) => { assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); diff --git a/crates/cli-support/src/js/wasi_intrinsics.rs b/crates/cli-support/src/js/wasi_intrinsics.rs new file mode 100644 index 00000000000..dad4bfe1d00 --- /dev/null +++ b/crates/cli-support/src/js/wasi_intrinsics.rs @@ -0,0 +1,126 @@ +use anyhow::{anyhow, bail, Error}; +use walrus::MemoryId; + +use crate::intrinsic::WasiIntrinsic; + +use super::Context; + +impl<'a> Context<'a> { + fn get_memory(&self) -> Result { + let mut memories = self.module.memories.iter(); + let memory = memories + .next() + .ok_or_else(|| anyhow!("no memory found to return in memory intrinsic"))? + .id(); + if memories.next().is_some() { + bail!( + "multiple memories found, unsure which to return \ + from memory intrinsic" + ); + } + Ok(memory) + } + + pub fn invoke_wasi_intrinsic( + &mut self, + intrinsic: &WasiIntrinsic, + args: &[String], + prelude: &mut String, + ) -> Result { + let expr = match intrinsic { + WasiIntrinsic::ClockTimeGet => { + assert_eq!(args.len(), 3); + + let mem = self.expose_int64_memory(self.get_memory()?); + let res_ptr = &args[2]; + + prelude.push_str(&format!( + " + let time = BigInt(new Date().getTime()); + {mem}()[{res_ptr} / 8] = time; + " + )); + + "0".to_string() + } + WasiIntrinsic::FdWrite => { + assert_eq!(args.len(), 4); + + "8".to_string() + } + WasiIntrinsic::FdRead => { + assert_eq!(args.len(), 4); + + "8".to_string() + } + WasiIntrinsic::FdSeek => "8".to_string(), + WasiIntrinsic::SchedYield => { + assert_eq!(args.len(), 0); + String::default() + } + WasiIntrinsic::RandomGet => { + assert_eq!(args.len(), 2); + + let mem = self.expose_uint8_memory(self.get_memory()?); + let ptr = &args[0]; + let len = &args[1]; + + prelude.push_str(&format!( + " + crypto.getRandomValues({mem}().subarray({ptr}, {ptr} + {len})); + " + )); + + "0".to_string() + } + WasiIntrinsic::EnvironGet => { + assert_eq!(args.len(), 2); + "0".to_string() + } + WasiIntrinsic::EnvironSizesGet => { + assert_eq!(args.len(), 2); + + let mem = self.expose_uint32_memory(self.get_memory()?); + let count_ptr = &args[0]; + let size_ptr = &args[1]; + + prelude.push_str(&format!( + " + {mem}()[{count_ptr} / 4] = 0; + {mem}()[{size_ptr} / 4] = 0; + " + )); + + "0".to_string() + } + WasiIntrinsic::FdClose => "8".to_string(), + WasiIntrinsic::FdFdStatGet => "8".to_string(), + WasiIntrinsic::FdFdStatSetFlags => "8".to_string(), + WasiIntrinsic::FdPrestatGet => "8".to_string(), + WasiIntrinsic::FdPrestatDirName => "8".to_string(), + WasiIntrinsic::PathOpen => "-1".to_string(), + WasiIntrinsic::ProcExit => { + let code = &args[0]; + format!("throw \"proc_exit called with code \" + {code};") + } + WasiIntrinsic::ArgsGet => "0".to_string(), + WasiIntrinsic::ArgsSizesGet => { + let mem = self.expose_uint32_memory(self.get_memory()?); + let count_ptr = &args[0]; + let size_ptr = &args[1]; + + prelude.push_str(&format!( + " + {mem}()[{count_ptr} / 4] = 0; + {mem}()[{size_ptr} / 4] = 0; + " + )); + + "0".to_string() + } + WasiIntrinsic::FdFileStatGet => "8".to_string(), + WasiIntrinsic::PathFileStatGet => "-1".to_string(), + }; + Ok(expr) + } +} diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 00800ca08a3..6b2e4d8754d 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -47,6 +47,7 @@ pub struct Bindgen { wasm_interface_types: bool, encode_into: EncodeInto, split_linked_modules: bool, + wasi_abi: bool, } pub struct Output { @@ -122,6 +123,7 @@ impl Bindgen { encode_into: EncodeInto::Test, omit_default_module_path: true, split_linked_modules: false, + wasi_abi: false, } } @@ -311,6 +313,11 @@ impl Bindgen { self } + pub fn wasi_abi(&mut self, enable: bool) -> &mut Bindgen { + self.wasi_abi = enable; + self + } + pub fn generate>(&mut self, path: P) -> Result<(), Error> { self.generate_output()?.emit(path.as_ref()) } @@ -381,6 +388,15 @@ impl Bindgen { // sections. descriptors::execute(&mut module)?; + if env::var("WASM_BINDGEN_EMULATE_WASI").is_ok() { + for import in module.imports.iter_mut() { + if import.module == "wasi_snapshot_preview1" { + import.module = PLACEHOLDER_MODULE.to_string(); + import.name = format!("__wbindgen_wasi_{}", import.name); + } + } + } + // Process the custom section we extracted earlier. In its stead insert // a forward-compatible wasm interface types section as well as an // auxiliary section for all sorts of miscellaneous information and @@ -393,6 +409,7 @@ impl Bindgen { self.wasm_interface_types, thread_count, self.emit_start, + self.wasi_abi, )?; // Now that we've got type information from the webidl processing pass, diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index a940f884dd5..0062ad61a17 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -114,6 +114,13 @@ impl InstructionBuilder<'_, '_> { }, &[AdapterType::I32, AdapterType::I32], ); + if self.cx.wasi_abi && !self.return_position { + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::PackSlice(self.cx.memory()?), + &[AdapterType::I32], + ); + } } Descriptor::Vector(_) => { @@ -129,6 +136,13 @@ impl InstructionBuilder<'_, '_> { }, &[AdapterType::I32, AdapterType::I32], ); + if self.cx.wasi_abi && !self.return_position { + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::PackSlice(self.cx.memory()?), + &[AdapterType::I32], + ); + } } // Can't be passed from JS to Rust yet @@ -187,6 +201,13 @@ impl InstructionBuilder<'_, '_> { }, &[AdapterType::I32, AdapterType::I32], ); + if self.cx.wasi_abi && !self.return_position { + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::PackSlice(self.cx.memory()?), + &[AdapterType::I32], + ); + } } Descriptor::Slice(_) => { // like strings, this allocation is cleaned up after being @@ -212,6 +233,13 @@ impl InstructionBuilder<'_, '_> { Instruction::I32FromExternrefOwned, &[AdapterType::I32], ); + if self.cx.wasi_abi && !self.return_position { + self.late_instruction( + &[AdapterType::I32, AdapterType::I32, AdapterType::I32], + Instruction::PackMutSlice(self.cx.memory()?), + &[AdapterType::I32], + ); + } } else { self.instruction( &[AdapterType::Vector(kind.clone())], @@ -222,6 +250,13 @@ impl InstructionBuilder<'_, '_> { }, &[AdapterType::I32, AdapterType::I32], ); + if self.cx.wasi_abi && !self.return_position { + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::PackSlice(self.cx.memory()?), + &[AdapterType::I32], + ); + } } } _ => bail!( @@ -256,11 +291,11 @@ impl InstructionBuilder<'_, '_> { Descriptor::U8 => self.in_option_sentinel(AdapterType::U8), Descriptor::I16 => self.in_option_sentinel(AdapterType::S16), Descriptor::U16 => self.in_option_sentinel(AdapterType::U16), - Descriptor::I32 => self.in_option_native(ValType::I32), - Descriptor::U32 => self.in_option_native(ValType::I32), - Descriptor::F32 => self.in_option_native(ValType::F32), - Descriptor::F64 => self.in_option_native(ValType::F64), - Descriptor::I64 | Descriptor::U64 => self.in_option_native(ValType::I64), + Descriptor::I32 => self.in_option_native(ValType::I32)?, + Descriptor::U32 => self.in_option_native(ValType::I32)?, + Descriptor::F32 => self.in_option_native(ValType::F32)?, + Descriptor::F64 => self.in_option_native(ValType::F64)?, + Descriptor::I64 | Descriptor::U64 => self.in_option_native(ValType::I64)?, Descriptor::Boolean => { self.instruction( &[AdapterType::Bool.option()], @@ -305,6 +340,13 @@ impl InstructionBuilder<'_, '_> { }, &[AdapterType::I32, AdapterType::I32], ); + if self.cx.wasi_abi && !self.return_position { + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::PackSlice(mem), + &[AdapterType::I32], + ); + } } Descriptor::Vector(_) => { @@ -321,6 +363,14 @@ impl InstructionBuilder<'_, '_> { Instruction::OptionVector { kind, malloc, mem }, &[AdapterType::I32, AdapterType::I32], ); + + if self.cx.wasi_abi && !self.return_position { + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::PackSlice(mem), + &[AdapterType::I32], + ); + } } _ => bail!( @@ -385,7 +435,7 @@ impl InstructionBuilder<'_, '_> { instr: Instruction, outputs: &[AdapterType], ) { - for input in inputs { + for input in inputs.iter().rev() { assert_eq!(self.output.pop().unwrap(), *input); } self.instructions.push(InstructionData { @@ -411,13 +461,21 @@ impl InstructionBuilder<'_, '_> { ); } - fn in_option_native(&mut self, wasm: ValType) { + fn in_option_native(&mut self, wasm: ValType) -> Result<(), Error> { let ty = AdapterType::from_wasm(wasm).unwrap(); self.instruction( &[ty.clone().option()], Instruction::FromOptionNative { ty: wasm }, - &[AdapterType::I32, ty], + &[AdapterType::I32, ty.clone()], ); + if self.cx.wasi_abi && !self.return_position { + self.late_instruction( + &[AdapterType::I32, ty.clone()], + Instruction::PackOption(self.cx.memory()?, ty), + &[AdapterType::I32], + ); + } + Ok(()) } fn in_option_sentinel(&mut self, ty: AdapterType) { diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 7986afb1c3a..0db4c4f288d 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -1,7 +1,7 @@ use crate::decode::LocalModule; use crate::descriptor::{Descriptor, Function}; use crate::descriptors::WasmBindgenDescriptorsSection; -use crate::intrinsic::Intrinsic; +use crate::intrinsic::{Intrinsic, WasiIntrinsic}; use crate::{decode, PLACEHOLDER_MODULE}; use anyhow::{anyhow, bail, Error}; use std::collections::{HashMap, HashSet}; @@ -38,6 +38,7 @@ struct Context<'a> { wasm_interface_types: bool, thread_count: Option, support_start: bool, + wasi_abi: bool, } struct InstructionBuilder<'a, 'b> { @@ -55,6 +56,7 @@ pub fn process( wasm_interface_types: bool, thread_count: Option, support_start: bool, + wasi_abi: bool, ) -> Result<(NonstandardWitSectionId, WasmBindgenAuxId), Error> { let mut cx = Context { adapters: Default::default(), @@ -72,6 +74,7 @@ pub fn process( wasm_interface_types, thread_count, support_start, + wasi_abi, }; cx.init()?; @@ -113,6 +116,7 @@ impl<'a> Context<'a> { // placeholder module name which we'll want to be sure that we've got a // location listed of what to import there for each item. let mut intrinsics = Vec::new(); + let mut wasi_intrinsics = Vec::new(); let mut duplicate_import_map = HashMap::new(); let mut imports_to_delete = HashSet::new(); for import in self.module.imports.iter() { @@ -146,10 +150,16 @@ impl<'a> Context<'a> { if let Some(intrinsic) = Intrinsic::from_symbol(&import.name) { intrinsics.push((import.id(), intrinsic)); } + if let Some(intrinsic) = WasiIntrinsic::from_symbol(&import.name) { + wasi_intrinsics.push((import.id(), intrinsic)); + } } for (id, intrinsic) in intrinsics { self.bind_intrinsic(id, intrinsic)?; } + for (id, intrinsic) in wasi_intrinsics { + self.bind_wasi_intrinsic(id, intrinsic)?; + } for import in imports_to_delete { self.module.imports.delete(import); } @@ -341,6 +351,14 @@ impl<'a> Context<'a> { Ok(()) } + fn bind_wasi_intrinsic(&mut self, id: ImportId, intrinsic: WasiIntrinsic) -> Result<(), Error> { + let id = self.import_adapter(id, intrinsic.signature(), AdapterJsImportKind::Normal)?; + self.aux + .import_map + .insert(id, AuxImport::WasiIntrinsic(intrinsic)); + Ok(()) + } + fn link_module( &mut self, id: ImportId, diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 46bcfedd8ff..c5a33df64c0 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -1,4 +1,4 @@ -use crate::intrinsic::Intrinsic; +use crate::intrinsic::{Intrinsic, WasiIntrinsic}; use crate::wit::AdapterId; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; @@ -329,6 +329,8 @@ pub enum AuxImport { /// shim. Each intrinsic has its own expected signature and implementation. Intrinsic(Intrinsic), + WasiIntrinsic(WasiIntrinsic), + /// This is a function which returns a URL pointing to a specific file, /// usually a JS snippet. The supplied path is relative to the JS glue shim. /// The Option may contain the contents of the linked file, so it can be diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index 1ea9c17bf93..193e50fa922 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -98,9 +98,20 @@ impl InstructionBuilder<'_, '_> { Descriptor::CachedString => self.cached_string(false, true)?, Descriptor::String => { - // fetch the ptr/length ... - self.get(AdapterType::I32); - self.get(AdapterType::I32); + if self.cx.wasi_abi && !self.return_position { + self.get(AdapterType::I32); + self.instructions.push(InstructionData { + instr: Instruction::UnpackSlice(self.cx.memory()?), + stack_change: StackChange::Modified { + popped: 1, + pushed: 2, + }, + }); + } else { + // fetch the ptr/length ... + self.get(AdapterType::I32); + self.get(AdapterType::I32); + } // ... then defer a call to `free` to happen later let free = self.cx.free()?; @@ -134,15 +145,32 @@ impl InstructionBuilder<'_, '_> { })?; let mem = self.cx.memory()?; let free = self.cx.free()?; - self.instruction( - &[AdapterType::I32, AdapterType::I32], - Instruction::VectorLoad { - kind: kind.clone(), - mem, - free, - }, - &[AdapterType::Vector(kind)], - ); + if self.cx.wasi_abi && !self.return_position { + self.instruction( + &[AdapterType::I32], + Instruction::UnpackSlice(mem), + &[AdapterType::I32, AdapterType::I32], + ); + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::VectorLoad { + kind: kind.clone(), + mem, + free, + }, + &[AdapterType::Vector(kind)], + ); + } else { + self.instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::VectorLoad { + kind: kind.clone(), + mem, + free, + }, + &[AdapterType::Vector(kind)], + ); + } } Descriptor::Option(d) => self.outgoing_option(d)?, @@ -181,12 +209,26 @@ impl InstructionBuilder<'_, '_> { Descriptor::CachedString => self.cached_string(false, false)?, Descriptor::String => { - let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?); - self.instruction( - &[AdapterType::I32, AdapterType::I32], - Instruction::Standard(std), - &[AdapterType::String], - ); + if self.cx.wasi_abi && !self.return_position { + self.instruction( + &[AdapterType::I32], + Instruction::UnpackSlice(self.cx.memory()?), + &[AdapterType::I32, AdapterType::I32], + ); + let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?); + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::Standard(std), + &[AdapterType::String], + ); + } else { + let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?); + self.instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::Standard(std), + &[AdapterType::String], + ); + } } Descriptor::Slice(_) => { let kind = arg.vector_kind().ok_or_else(|| { @@ -196,14 +238,30 @@ impl InstructionBuilder<'_, '_> { ) })?; let mem = self.cx.memory()?; - self.instruction( - &[AdapterType::I32, AdapterType::I32], - Instruction::View { - kind: kind.clone(), - mem, - }, - &[AdapterType::Vector(kind)], - ); + if self.cx.wasi_abi && !self.return_position { + self.instruction( + &[AdapterType::I32], + Instruction::UnpackSlice(self.cx.memory()?), + &[AdapterType::I32, AdapterType::I32], + ); + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::View { + kind: kind.clone(), + mem, + }, + &[AdapterType::Vector(kind)], + ); + } else { + self.instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::View { + kind: kind.clone(), + mem, + }, + &[AdapterType::Vector(kind)], + ); + } } Descriptor::Function(descriptor) => { @@ -216,15 +274,32 @@ impl InstructionBuilder<'_, '_> { let adapter = self .cx .table_element_adapter(descriptor.shim_idx, descriptor)?; - self.instruction( - &[AdapterType::I32, AdapterType::I32], - Instruction::StackClosure { - adapter, - nargs, - mutable, - }, - &[AdapterType::Function], - ); + if self.cx.wasi_abi && !self.return_position { + self.instruction( + &[AdapterType::I32], + Instruction::UnpackSlice(self.cx.memory()?), + &[AdapterType::I32, AdapterType::I32], + ); + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::StackClosure { + adapter, + nargs, + mutable, + }, + &[AdapterType::Function], + ); + } else { + self.instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::StackClosure { + adapter, + nargs, + mutable, + }, + &[AdapterType::Function], + ); + } } _ => bail!( @@ -261,12 +336,12 @@ impl InstructionBuilder<'_, '_> { Descriptor::U8 => self.out_option_sentinel(AdapterType::U8), Descriptor::I16 => self.out_option_sentinel(AdapterType::S16), Descriptor::U16 => self.out_option_sentinel(AdapterType::U16), - Descriptor::I32 => self.option_native(true, ValType::I32), - Descriptor::U32 => self.option_native(false, ValType::I32), - Descriptor::I64 => self.option_native(true, ValType::I64), - Descriptor::U64 => self.option_native(false, ValType::I64), - Descriptor::F32 => self.option_native(true, ValType::F32), - Descriptor::F64 => self.option_native(true, ValType::F64), + Descriptor::I32 => self.option_native(true, ValType::I32)?, + Descriptor::U32 => self.option_native(false, ValType::I32)?, + Descriptor::I64 => self.option_native(true, ValType::I64)?, + Descriptor::U64 => self.option_native(false, ValType::I64)?, + Descriptor::F32 => self.option_native(true, ValType::F32)?, + Descriptor::F64 => self.option_native(true, ValType::F64)?, Descriptor::Boolean => { self.instruction( &[AdapterType::I32], @@ -311,15 +386,32 @@ impl InstructionBuilder<'_, '_> { })?; let mem = self.cx.memory()?; let free = self.cx.free()?; - self.instruction( - &[AdapterType::I32, AdapterType::I32], - Instruction::OptionVectorLoad { - kind: kind.clone(), - mem, - free, - }, - &[AdapterType::Vector(kind).option()], - ); + if self.cx.wasi_abi && !self.return_position { + self.instruction( + &[AdapterType::I32], + Instruction::UnpackSlice(mem), + &[AdapterType::I32, AdapterType::I32], + ); + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::OptionVectorLoad { + kind: kind.clone(), + mem, + free, + }, + &[AdapterType::Vector(kind).option()], + ); + } else { + self.instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::OptionVectorLoad { + kind: kind.clone(), + mem, + free, + }, + &[AdapterType::Vector(kind).option()], + ); + } } _ => bail!( @@ -492,14 +584,30 @@ impl InstructionBuilder<'_, '_> { ) })?; let mem = self.cx.memory()?; - self.instruction( - &[AdapterType::I32, AdapterType::I32], - Instruction::OptionView { - kind: kind.clone(), - mem, - }, - &[AdapterType::Vector(kind).option()], - ); + if self.cx.wasi_abi && !self.return_position { + self.instruction( + &[AdapterType::I32], + Instruction::UnpackSlice(mem), + &[AdapterType::I32, AdapterType::I32], + ); + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::OptionView { + kind: kind.clone(), + mem, + }, + &[AdapterType::Vector(kind).option()], + ); + } else { + self.instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::OptionView { + kind: kind.clone(), + mem, + }, + &[AdapterType::Vector(kind).option()], + ); + } } _ => bail!( "unsupported optional ref argument type for calling JS function from Rust: {:?}", @@ -530,27 +638,60 @@ impl InstructionBuilder<'_, '_> { fn cached_string(&mut self, optional: bool, owned: bool) -> Result<(), Error> { let mem = self.cx.memory()?; let free = self.cx.free()?; - self.instruction( - &[AdapterType::I32, AdapterType::I32], - Instruction::CachedStringLoad { - owned, - optional, - mem, - free, - table: None, - }, - &[AdapterType::String], - ); + if self.cx.wasi_abi && !self.return_position { + self.instruction( + &[AdapterType::I32], + Instruction::UnpackSlice(mem), + &[AdapterType::I32, AdapterType::I32], + ); + self.late_instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::CachedStringLoad { + owned, + optional, + mem, + free, + table: None, + }, + &[AdapterType::String], + ); + } else { + self.instruction( + &[AdapterType::I32, AdapterType::I32], + Instruction::CachedStringLoad { + owned, + optional, + mem, + free, + table: None, + }, + &[AdapterType::String], + ); + } Ok(()) } - fn option_native(&mut self, signed: bool, ty: ValType) { + fn option_native(&mut self, signed: bool, ty: ValType) -> Result<(), Error> { let adapter_ty = AdapterType::from_wasm(ty).unwrap(); - self.instruction( - &[AdapterType::I32, adapter_ty.clone()], - Instruction::ToOptionNative { signed, ty }, - &[adapter_ty.option()], - ); + if self.cx.wasi_abi && !self.return_position { + self.instruction( + &[AdapterType::I32], + Instruction::UnpackOption(self.cx.memory()?, adapter_ty.clone()), + &[AdapterType::I32, adapter_ty.clone()], + ); + self.late_instruction( + &[AdapterType::I32, adapter_ty.clone()], + Instruction::ToOptionNative { signed, ty }, + &[adapter_ty.option()], + ); + } else { + self.instruction( + &[AdapterType::I32, adapter_ty.clone()], + Instruction::ToOptionNative { signed, ty }, + &[adapter_ty.option()], + ); + } + Ok(()) } fn out_option_sentinel(&mut self, ty: AdapterType) { diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index bd1fb23a2e7..213c623c49a 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -236,6 +236,14 @@ fn translate_instruction( mem: *mem, malloc: *malloc, }), + PackSlice(_) | PackMutSlice(_) | UnpackSlice(_) => { + bail!("slices not supported in wasm interface types with wasi ABI"); + } + PackOption(_, _) | UnpackOption(_, _) => { + bail!( + "Options with primitive types not supported in wasm interface types with wasi ABI" + ); + } StoreRetptr { .. } | LoadRetptr { .. } | Retptr { .. } => { bail!("return pointers aren't supported in wasm interface types"); } @@ -355,6 +363,9 @@ fn check_standard_import(import: &AuxImport) -> Result<(), Error> { AuxImport::Intrinsic(intrinsic) => { format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name()) } + AuxImport::WasiIntrinsic(intrinsic) => { + format!("wasm-bindgen wasi intrinsic `{}`", intrinsic.name()) + } AuxImport::LinkTo(path, _) => { format!("wasm-bindgen specific link function for `{}`", path) } diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 7dc98a42beb..bc38f2e4b6a 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -94,6 +94,13 @@ pub enum Instruction { /// A known instruction in the "standard" Standard(wit_walrus::Instruction), + PackSlice(walrus::MemoryId), + PackMutSlice(walrus::MemoryId), + UnpackSlice(walrus::MemoryId), + + PackOption(walrus::MemoryId, AdapterType), + UnpackOption(walrus::MemoryId, AdapterType), + /// A call to one of our own defined adapters, similar to the standard /// call-adapter instruction CallAdapter(AdapterId), diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs index 3286a118cc7..2adc7c82f5b 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -171,6 +171,11 @@ integration test.\ b.split_linked_modules(true); } + if env::var("TEST_WASI_ABI").is_ok() { + b.wasi_abi(true); + env::set_var("WASM_BINDGEN_EMULATE_WASI", "1"); + } + b.debug(debug) .input_module(module, wasm) .keep_debug(false) diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs index 94177b945a2..3065f04bbdb 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs @@ -120,6 +120,12 @@ pub fn spawn( if !response.is_success() { response = try_asset(&request, ".".as_ref()); } + if !response.is_success() { + response = try_asset( + &request, + concat!(env!("CARGO_MANIFEST_DIR"), "/../../").as_ref(), + ); + } // Make sure browsers don't cache anything (Chrome appeared to with this // header?) response.headers.retain(|(k, _)| k != "Cache-Control"); diff --git a/crates/cli/src/bin/wasm-bindgen.rs b/crates/cli/src/bin/wasm-bindgen.rs index e5542af5e27..df105e1c4bd 100644 --- a/crates/cli/src/bin/wasm-bindgen.rs +++ b/crates/cli/src/bin/wasm-bindgen.rs @@ -41,6 +41,7 @@ Options: --no-modules Deprecated, use `--target no-modules` --weak-refs Enable usage of the JS weak references proposal --reference-types Enable usage of WebAssembly reference types + --wasi-abi Generate bindings for the wasm32-wasi target -V --version Print the version number of wasm-bindgen Additional documentation: https://rustwasm.github.io/wasm-bindgen/reference/cli.html @@ -71,6 +72,7 @@ struct Args { flag_target: Option, flag_omit_default_module_path: bool, flag_split_linked_modules: bool, + flag_wasi_abi: bool, arg_input: Option, } @@ -125,7 +127,8 @@ fn rmain(args: &Args) -> Result<(), Error> { .typescript(typescript) .omit_imports(args.flag_omit_imports) .omit_default_module_path(args.flag_omit_default_module_path) - .split_linked_modules(args.flag_split_linked_modules); + .split_linked_modules(args.flag_split_linked_modules) + .wasi_abi(args.flag_wasi_abi); if let Some(true) = args.flag_weak_refs { b.weak_refs(true); } diff --git a/src/convert/slices.rs b/src/convert/slices.rs index 58608b8c520..c4b48ef13cb 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -51,8 +51,10 @@ if_std! { fn drop(&mut self) { unsafe { __wbindgen_copy_to_typed_array( - self.contents.as_ptr() as *const u8, - self.contents.len() * mem::size_of::(), + WasmSlice { + ptr: self.contents.as_ptr() as u32, + len: (self.contents.len() * mem::size_of::()) as u32, + }, self.js.idx ); } diff --git a/src/lib.rs b/src/lib.rs index f8576ed4c3e..d04260b8b48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,7 +140,12 @@ impl JsValue { /// be owned by the JS garbage collector. #[inline] pub fn from_str(s: &str) -> JsValue { - unsafe { JsValue::_new(__wbindgen_string_new(s.as_ptr(), s.len())) } + unsafe { + JsValue::_new(__wbindgen_string_new(WasmSlice { + ptr: s.as_ptr() as u32, + len: s.len() as u32, + })) + } } /// Creates a new JS value which is a number. @@ -158,7 +163,12 @@ impl JsValue { /// allocated large integer) and returns a handle to the JS version of it. #[inline] pub fn bigint_from_str(s: &str) -> JsValue { - unsafe { JsValue::_new(__wbindgen_bigint_from_str(s.as_ptr(), s.len())) } + unsafe { + JsValue::_new(__wbindgen_bigint_from_str(WasmSlice { + ptr: s.as_ptr() as u32, + len: s.len() as u32, + })) + } } /// Creates a new JS value which is a boolean. @@ -193,10 +203,10 @@ impl JsValue { pub fn symbol(description: Option<&str>) -> JsValue { unsafe { match description { - Some(description) => JsValue::_new(__wbindgen_symbol_named_new( - description.as_ptr(), - description.len(), - )), + Some(description) => JsValue::_new(__wbindgen_symbol_named_new(WasmSlice { + ptr: description.as_ptr() as u32, + len: description.len() as u32, + })), None => JsValue::_new(__wbindgen_symbol_anonymous_new()), } } @@ -232,7 +242,12 @@ impl JsValue { T: serde::ser::Serialize + ?Sized, { let s = serde_json::to_string(t)?; - unsafe { Ok(JsValue::_new(__wbindgen_json_parse(s.as_ptr(), s.len()))) } + unsafe { + Ok(JsValue::_new(__wbindgen_json_parse(WasmSlice { + ptr: s.as_ptr() as u32, + len: s.len() as u32, + }))) + } } /// Invokes `JSON.stringify` on this value and then parses the resulting @@ -1010,14 +1025,14 @@ externs! { fn __wbindgen_object_clone_ref(idx: u32) -> u32; fn __wbindgen_object_drop_ref(idx: u32) -> (); - fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_string_new(s: WasmSlice) -> u32; fn __wbindgen_number_new(f: f64) -> u32; - fn __wbindgen_bigint_from_str(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_bigint_from_str(s: WasmSlice) -> u32; fn __wbindgen_bigint_from_i64(n: i64) -> u32; fn __wbindgen_bigint_from_u64(n: u64) -> u32; fn __wbindgen_bigint_from_i128(hi: i64, lo: u64) -> u32; fn __wbindgen_bigint_from_u128(hi: u64, lo: u64) -> u32; - fn __wbindgen_symbol_named_new(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_symbol_named_new(name: WasmSlice) -> u32; fn __wbindgen_symbol_anonymous_new() -> u32; fn __wbindgen_externref_heap_live_count() -> u32; @@ -1064,21 +1079,21 @@ externs! { fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> (); - fn __wbindgen_throw(a: *const u8, b: usize) -> !; + fn __wbindgen_throw(msg: WasmSlice) -> !; fn __wbindgen_rethrow(a: u32) -> !; - fn __wbindgen_error_new(a: *const u8, b: usize) -> u32; + fn __wbindgen_error_new(msg: WasmSlice) -> u32; fn __wbindgen_cb_drop(idx: u32) -> u32; fn __wbindgen_describe(v: u32) -> (); fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32; - fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_json_parse(s: WasmSlice) -> u32; fn __wbindgen_json_serialize(idx: u32) -> WasmSlice; fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32; fn __wbindgen_jsval_loose_eq(a: u32, b: u32) -> u32; - fn __wbindgen_copy_to_typed_array(ptr: *const u8, len: usize, idx: u32) -> (); + fn __wbindgen_copy_to_typed_array(buffer: WasmSlice, idx: u32) -> (); fn __wbindgen_not(idx: u32) -> u32; @@ -1199,7 +1214,10 @@ pub fn throw(s: &str) -> ! { #[inline(never)] pub fn throw_str(s: &str) -> ! { unsafe { - __wbindgen_throw(s.as_ptr(), s.len()); + __wbindgen_throw(WasmSlice { + ptr: s.as_ptr() as u32, + len: s.len() as u32, + }); } } @@ -1825,7 +1843,12 @@ impl JsError { #[inline] pub fn new(s: &str) -> JsError { Self { - value: unsafe { JsValue::_new(crate::__wbindgen_error_new(s.as_ptr(), s.len())) }, + value: unsafe { + JsValue::_new(crate::__wbindgen_error_new(WasmSlice { + ptr: s.as_ptr() as u32, + len: s.len() as u32, + })) + }, } } }