From 8d5b15e12b6ee23f16b6d1aa00ec27c5e1bb23d2 Mon Sep 17 00:00:00 2001 From: Robin Quint Date: Sun, 19 Feb 2023 18:38:09 +0100 Subject: [PATCH 1/5] Implemented compatibility mode with wasm32-wasi --- .cargo/config | 3 + .github/workflows/main.yml | 44 +++ crates/cli-support/src/js/binding.rs | 108 +++++++ crates/cli-support/src/lib.rs | 15 + crates/cli-support/src/wit/incoming.rs | 74 ++++- crates/cli-support/src/wit/mod.rs | 3 + crates/cli-support/src/wit/outgoing.rs | 291 +++++++++++++----- crates/cli-support/src/wit/section.rs | 6 + crates/cli-support/src/wit/standard.rs | 7 + .../src/bin/wasm-bindgen-test-runner/main.rs | 4 + crates/cli/src/bin/wasm-bindgen.rs | 5 +- src/convert/slices.rs | 6 +- src/lib.rs | 30 +- tests/wasi/wasi_browser.js | 72 +++++ tests/wasi/wasi_node.js | 73 +++++ 15 files changed, 641 insertions(+), 100 deletions(-) create mode 100644 tests/wasi/wasi_browser.js create mode 100644 tests/wasi/wasi_node.js 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..5f4e9b8e0ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,6 +71,50 @@ 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 + env: + REROUTE_WASI_SNAPSHOT_PREVIEW1: ./tests/wasi/wasi_browser.js + - run: cargo test --target wasm32-wasi --features serde-serialize + env: + REROUTE_WASI_SNAPSHOT_PREVIEW1: ./tests/wasi/wasi_browser.js + - run: cargo test --target wasm32-wasi --features enable-interning + env: + REROUTE_WASI_SNAPSHOT_PREVIEW1: ./tests/wasi/wasi_browser.js + - 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 + REROUTE_WASI_SNAPSHOT_PREVIEW1: tests/wasi/wasi_node.js + - run: cargo test --target wasm32-wasi --test wasm + env: + WASM_BINDGEN_WEAKREF: 1 + WASM_BINDGEN_NO_DEBUG: 1 + REROUTE_WASI_SNAPSHOT_PREVIEW1: tests/wasi/wasi_node.js + - run: cargo test --target wasm32-wasi --test wasm --features serde-serialize + env: + WASM_BINDGEN_WEAKREF: 1 + REROUTE_WASI_SNAPSHOT_PREVIEW1: tests/wasi/wasi_node.js + - run: cargo test --target wasm32-wasi + env: + WASM_BINDGEN_EXTERNREF: 1 + NODE_ARGS: --experimental-wasm-reftypes + REROUTE_WASI_SNAPSHOT_PREVIEW1: tests/wasi/wasi_node.js + test_threads: name: "Run wasm-bindgen crate tests with multithreading enabled" runs-on: ubuntu-latest diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 0723928ee9f..694ac2b9ab7 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -633,6 +633,114 @@ 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/lib.rs b/crates/cli-support/src/lib.rs index 00800ca08a3..82011a37546 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,13 @@ impl Bindgen { // sections. descriptors::execute(&mut module)?; + for import in module.imports.iter_mut() { + let env_name = format!("REROUTE_{}", import.module.to_uppercase()); + if let Ok(reroute) = env::var(env_name) { + import.module = reroute; + } + } + // 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 +407,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..177aeafcb00 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -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()?; 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..917103a0dee 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -236,6 +236,12 @@ 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"); } 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..54ffa032c65 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,10 @@ integration test.\ b.split_linked_modules(true); } + if env::var("TEST_WASI_ABI").is_ok() { + b.wasi_abi(true); + } + b.debug(debug) .input_module(module, wasm) .keep_debug(false) 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..ad3b3c7e85b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,7 +140,7 @@ 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 +158,7 @@ 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. @@ -194,8 +194,10 @@ impl JsValue { unsafe { match description { Some(description) => JsValue::_new(__wbindgen_symbol_named_new( - description.as_ptr(), - description.len(), + WasmSlice { + ptr: description.as_ptr() as u32, + len: description.len() as u32, + }, )), None => JsValue::_new(__wbindgen_symbol_anonymous_new()), } @@ -232,7 +234,7 @@ 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 +1012,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 +1066,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 +1201,7 @@ 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 +1827,7 @@ 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 })) }, } } } diff --git a/tests/wasi/wasi_browser.js b/tests/wasi/wasi_browser.js new file mode 100644 index 00000000000..3b2c51ff5b2 --- /dev/null +++ b/tests/wasi/wasi_browser.js @@ -0,0 +1,72 @@ + +export function args_get() { + return 0; +} + +export function args_sizes_get() { + return 0; +} + +export function clock_time_get() { + return 0; +} + +export function fd_filestat_get() { + return 0; +} + +export function fd_read() { + return 0; +} + +export function fd_seek() { + return 0; +} + +export function fd_write() { + return 0; +} + +export function path_filestat_get() { + return 0; +} + +export function path_open() { + return 0; +} + +export function sched_yield() { + return 0; +} + +export function random_get() { + return 0; +} + +export function environ_get() { + return 0; +} + +export function environ_sizes_get() { + return 0; +} + +export function fd_close() { + return 0; +} + +export function fd_fdstat_get() { + return 0; +} + +export function fd_prestat_get() { + return 8; +} + +export function fd_prestat_dir_name() { + return 0; +} + +export function proc_exit() { + throw "proc_exit"; +} diff --git a/tests/wasi/wasi_node.js b/tests/wasi/wasi_node.js new file mode 100644 index 00000000000..a1e846c7e77 --- /dev/null +++ b/tests/wasi/wasi_node.js @@ -0,0 +1,73 @@ +module.exports = {}; + +module.exports.args_get = function() { + return 0; +} + +module.exports.args_sizes_get = function() { + return 0; +} + +module.exports.clock_time_get = function() { + return 0; +} + +module.exports.fd_filestat_get = function() { + return 0; +} + +module.exports.fd_read = function() { + return 0; +} + +module.exports.fd_seek = function() { + return 0; +} + +module.exports.fd_write = function() { + return 0; +} + +module.exports.path_filestat_get = function() { + return 0; +} + +module.exports.path_open = function() { + return 0; +} + +module.exports.sched_yield = function() { + return 0; +} + +module.exports.random_get = function() { + return 0; +} + +module.exports.environ_get = function() { + return 0; +} + +module.exports.environ_sizes_get = function() { + return 0; +} + +module.exports.fd_close = function() { + return 0; +} + +module.exports.fd_fdstat_get = function() { + return 0; +} + +module.exports.fd_prestat_get = function() { + return 8; +} + +module.exports.fd_prestat_dir_name = function() { + return 0; +} + +module.exports.proc_exit = function() { + throw "proc_exit"; +} From 641057eba01e3ed10dc65a3bf306e87e9b23b671 Mon Sep 17 00:00:00 2001 From: Robin Quint Date: Sun, 19 Feb 2023 18:52:19 +0100 Subject: [PATCH 2/5] Cargo fmt --- crates/cli-support/src/js/binding.rs | 30 ++++++++++++------- crates/cli-support/src/wit/section.rs | 4 ++- src/lib.rs | 43 ++++++++++++++++++++------- 3 files changed, 55 insertions(+), 22 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 694ac2b9ab7..416a70e65ec 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -642,11 +642,13 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> let i = js.tmp(); let mem = js.cx.expose_uint32_memory(*mem); - js.prelude(&format!(" + 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}")); @@ -662,12 +664,14 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> let i = js.tmp(); let mem = js.cx.expose_uint32_memory(*mem); - js.prelude(&format!(" + 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}")); @@ -679,10 +683,12 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> let arg_ptr = js.pop(); let i = js.tmp(); - js.prelude(&format!(" + 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}")); @@ -707,11 +713,13 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> _ => bail!("Unexpected type passed to PackOption"), }; - js.prelude(&format!(" + 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}")); @@ -732,10 +740,12 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> let arg_ptr = js.pop(); let i = js.tmp(); - js.prelude(&format!(" + 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}")); diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index 917103a0dee..50d8b3e76e6 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -240,7 +240,9 @@ fn translate_instruction( 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"); + 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"); diff --git a/src/lib.rs b/src/lib.rs index ad3b3c7e85b..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(WasmSlice { ptr: s.as_ptr() as u32, len: s.len() as u32 })) } + 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(WasmSlice { ptr: s.as_ptr() as u32, len: s.len() as u32 })) } + 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,12 +203,10 @@ impl JsValue { pub fn symbol(description: Option<&str>) -> JsValue { unsafe { match description { - Some(description) => JsValue::_new(__wbindgen_symbol_named_new( - WasmSlice { - ptr: description.as_ptr() as u32, - len: description.len() as u32, - }, - )), + 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()), } } @@ -234,7 +242,12 @@ impl JsValue { T: serde::ser::Serialize + ?Sized, { let s = serde_json::to_string(t)?; - unsafe { Ok(JsValue::_new(__wbindgen_json_parse(WasmSlice { ptr: s.as_ptr() as u32, len: s.len() as u32 }))) } + 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 @@ -1201,7 +1214,10 @@ pub fn throw(s: &str) -> ! { #[inline(never)] pub fn throw_str(s: &str) -> ! { unsafe { - __wbindgen_throw(WasmSlice { ptr: s.as_ptr() as u32, len: s.len() as u32 }); + __wbindgen_throw(WasmSlice { + ptr: s.as_ptr() as u32, + len: s.len() as u32, + }); } } @@ -1827,7 +1843,12 @@ impl JsError { #[inline] pub fn new(s: &str) -> JsError { Self { - value: unsafe { JsValue::_new(crate::__wbindgen_error_new(WasmSlice { ptr: s.as_ptr() as u32, len: s.len() as u32 })) }, + value: unsafe { + JsValue::_new(crate::__wbindgen_error_new(WasmSlice { + ptr: s.as_ptr() as u32, + len: s.len() as u32, + })) + }, } } } From 7e6d039b4ef9942ec652ed8de3f20a057ceffa9d Mon Sep 17 00:00:00 2001 From: Robin Quint Date: Mon, 20 Feb 2023 20:22:12 +0100 Subject: [PATCH 3/5] Improved test cases --- .github/workflows/main.yml | 12 +----------- crates/cli/src/bin/wasm-bindgen-test-runner/main.rs | 6 ++++++ .../cli/src/bin/wasm-bindgen-test-runner/server.rs | 3 +++ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5f4e9b8e0ad..c70e06702b0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -86,34 +86,24 @@ jobs: node-version: '16' - uses: ./.github/actions/setup-geckodriver - run: cargo test --target wasm32-wasi - env: - REROUTE_WASI_SNAPSHOT_PREVIEW1: ./tests/wasi/wasi_browser.js - run: cargo test --target wasm32-wasi --features serde-serialize - env: - REROUTE_WASI_SNAPSHOT_PREVIEW1: ./tests/wasi/wasi_browser.js - run: cargo test --target wasm32-wasi --features enable-interning - env: - REROUTE_WASI_SNAPSHOT_PREVIEW1: ./tests/wasi/wasi_browser.js - 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 -p wasm-bindgen-futures - run: cargo test --target wasm32-wasi --test wasm env: WASM_BINDGEN_WEAKREF: 1 - REROUTE_WASI_SNAPSHOT_PREVIEW1: tests/wasi/wasi_node.js - run: cargo test --target wasm32-wasi --test wasm env: WASM_BINDGEN_WEAKREF: 1 WASM_BINDGEN_NO_DEBUG: 1 - REROUTE_WASI_SNAPSHOT_PREVIEW1: tests/wasi/wasi_node.js - run: cargo test --target wasm32-wasi --test wasm --features serde-serialize env: WASM_BINDGEN_WEAKREF: 1 - REROUTE_WASI_SNAPSHOT_PREVIEW1: tests/wasi/wasi_node.js - run: cargo test --target wasm32-wasi env: WASM_BINDGEN_EXTERNREF: 1 NODE_ARGS: --experimental-wasm-reftypes - REROUTE_WASI_SNAPSHOT_PREVIEW1: tests/wasi/wasi_node.js test_threads: name: "Run wasm-bindgen crate tests with multithreading enabled" 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 54ffa032c65..28a6e85201b 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -173,6 +173,12 @@ integration test.\ if env::var("TEST_WASI_ABI").is_ok() { b.wasi_abi(true); + + match test_mode { + TestMode::Node => env::set_var("REROUTE_WASI_SNAPSHOT_PREVIEW1", concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/wasi/wasi_node.js")), + TestMode::Browser => env::set_var("REROUTE_WASI_SNAPSHOT_PREVIEW1", "./tests/wasi/wasi_browser.js"), + _ => {}, + } } b.debug(debug) 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..980d8fd8a87 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,9 @@ 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"); From f5928e5656f7833f47d660cc0f3387652c63a12b Mon Sep 17 00:00:00 2001 From: Robin Quint Date: Tue, 21 Feb 2023 19:56:10 +0100 Subject: [PATCH 4/5] Added WASM_BINDGEN_EMULATE_WASI flag --- crates/cli-support/src/intrinsic.rs | 76 +++++++++- crates/cli-support/src/js/mod.rs | 7 + crates/cli-support/src/js/wasi_intrinsics.rs | 138 ++++++++++++++++++ crates/cli-support/src/lib.rs | 10 +- crates/cli-support/src/wit/mod.rs | 17 ++- crates/cli-support/src/wit/nonstandard.rs | 4 +- crates/cli-support/src/wit/section.rs | 3 + .../src/bin/wasm-bindgen-test-runner/main.rs | 7 +- tests/wasi/wasi_browser.js | 72 --------- tests/wasi/wasi_node.js | 73 --------- 10 files changed, 243 insertions(+), 164 deletions(-) create mode 100644 crates/cli-support/src/js/wasi_intrinsics.rs delete mode 100644 tests/wasi/wasi_browser.js delete mode 100644 tests/wasi/wasi_node.js diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index e8874d5e055..df3952b8ef1 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/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..13d4a8eb7af --- /dev/null +++ b/crates/cli-support/src/js/wasi_intrinsics.rs @@ -0,0 +1,138 @@ +use anyhow::{Error, bail, anyhow}; +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 82011a37546..6b2e4d8754d 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -388,10 +388,12 @@ impl Bindgen { // sections. descriptors::execute(&mut module)?; - for import in module.imports.iter_mut() { - let env_name = format!("REROUTE_{}", import.module.to_uppercase()); - if let Ok(reroute) = env::var(env_name) { - import.module = reroute; + 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); + } } } diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 177aeafcb00..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}; @@ -116,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() { @@ -149,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); } @@ -344,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/section.rs b/crates/cli-support/src/wit/section.rs index 50d8b3e76e6..213c623c49a 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -363,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/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs index 28a6e85201b..2adc7c82f5b 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -173,12 +173,7 @@ integration test.\ if env::var("TEST_WASI_ABI").is_ok() { b.wasi_abi(true); - - match test_mode { - TestMode::Node => env::set_var("REROUTE_WASI_SNAPSHOT_PREVIEW1", concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/wasi/wasi_node.js")), - TestMode::Browser => env::set_var("REROUTE_WASI_SNAPSHOT_PREVIEW1", "./tests/wasi/wasi_browser.js"), - _ => {}, - } + env::set_var("WASM_BINDGEN_EMULATE_WASI", "1"); } b.debug(debug) diff --git a/tests/wasi/wasi_browser.js b/tests/wasi/wasi_browser.js deleted file mode 100644 index 3b2c51ff5b2..00000000000 --- a/tests/wasi/wasi_browser.js +++ /dev/null @@ -1,72 +0,0 @@ - -export function args_get() { - return 0; -} - -export function args_sizes_get() { - return 0; -} - -export function clock_time_get() { - return 0; -} - -export function fd_filestat_get() { - return 0; -} - -export function fd_read() { - return 0; -} - -export function fd_seek() { - return 0; -} - -export function fd_write() { - return 0; -} - -export function path_filestat_get() { - return 0; -} - -export function path_open() { - return 0; -} - -export function sched_yield() { - return 0; -} - -export function random_get() { - return 0; -} - -export function environ_get() { - return 0; -} - -export function environ_sizes_get() { - return 0; -} - -export function fd_close() { - return 0; -} - -export function fd_fdstat_get() { - return 0; -} - -export function fd_prestat_get() { - return 8; -} - -export function fd_prestat_dir_name() { - return 0; -} - -export function proc_exit() { - throw "proc_exit"; -} diff --git a/tests/wasi/wasi_node.js b/tests/wasi/wasi_node.js deleted file mode 100644 index a1e846c7e77..00000000000 --- a/tests/wasi/wasi_node.js +++ /dev/null @@ -1,73 +0,0 @@ -module.exports = {}; - -module.exports.args_get = function() { - return 0; -} - -module.exports.args_sizes_get = function() { - return 0; -} - -module.exports.clock_time_get = function() { - return 0; -} - -module.exports.fd_filestat_get = function() { - return 0; -} - -module.exports.fd_read = function() { - return 0; -} - -module.exports.fd_seek = function() { - return 0; -} - -module.exports.fd_write = function() { - return 0; -} - -module.exports.path_filestat_get = function() { - return 0; -} - -module.exports.path_open = function() { - return 0; -} - -module.exports.sched_yield = function() { - return 0; -} - -module.exports.random_get = function() { - return 0; -} - -module.exports.environ_get = function() { - return 0; -} - -module.exports.environ_sizes_get = function() { - return 0; -} - -module.exports.fd_close = function() { - return 0; -} - -module.exports.fd_fdstat_get = function() { - return 0; -} - -module.exports.fd_prestat_get = function() { - return 8; -} - -module.exports.fd_prestat_dir_name = function() { - return 0; -} - -module.exports.proc_exit = function() { - throw "proc_exit"; -} From e0e55ef696fa755a480a17008268a6346f32b7f6 Mon Sep 17 00:00:00 2001 From: Robin Quint Date: Tue, 21 Feb 2023 19:56:30 +0100 Subject: [PATCH 5/5] Cargo fmt --- crates/cli-support/src/intrinsic.rs | 2 +- crates/cli-support/src/js/wasi_intrinsics.rs | 68 ++++++++----------- .../bin/wasm-bindgen-test-runner/server.rs | 5 +- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index df3952b8ef1..3c66af16a47 100644 --- a/crates/cli-support/src/intrinsic.rs +++ b/crates/cli-support/src/intrinsic.rs @@ -279,7 +279,7 @@ intrinsics! { } } -intrinsics!{ +intrinsics! { pub enum WasiIntrinsic { #[symbol = "__wbindgen_wasi_clock_time_get"] #[signature = fn(I32, I64, I32) -> I32] diff --git a/crates/cli-support/src/js/wasi_intrinsics.rs b/crates/cli-support/src/js/wasi_intrinsics.rs index 13d4a8eb7af..dad4bfe1d00 100644 --- a/crates/cli-support/src/js/wasi_intrinsics.rs +++ b/crates/cli-support/src/js/wasi_intrinsics.rs @@ -1,4 +1,4 @@ -use anyhow::{Error, bail, anyhow}; +use anyhow::{anyhow, bail, Error}; use walrus::MemoryId; use crate::intrinsic::WasiIntrinsic; @@ -34,13 +34,15 @@ impl<'a> Context<'a> { let mem = self.expose_int64_memory(self.get_memory()?); let res_ptr = &args[2]; - prelude.push_str(&format!(" + 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); @@ -51,9 +53,7 @@ impl<'a> Context<'a> { "8".to_string() } - WasiIntrinsic::FdSeek => { - "8".to_string() - } + WasiIntrinsic::FdSeek => "8".to_string(), WasiIntrinsic::SchedYield => { assert_eq!(args.len(), 0); String::default() @@ -65,9 +65,11 @@ impl<'a> Context<'a> { let ptr = &args[0]; let len = &args[1]; - prelude.push_str(&format!(" + prelude.push_str(&format!( + " crypto.getRandomValues({mem}().subarray({ptr}, {ptr} + {len})); - ")); + " + )); "0".to_string() } @@ -82,56 +84,42 @@ impl<'a> Context<'a> { let count_ptr = &args[0]; let size_ptr = &args[1]; - prelude.push_str(&format!(" + 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::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::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!(" + 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() - } + WasiIntrinsic::FdFileStatGet => "8".to_string(), + WasiIntrinsic::PathFileStatGet => "-1".to_string(), }; Ok(expr) } 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 980d8fd8a87..3065f04bbdb 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs @@ -121,7 +121,10 @@ pub fn spawn( response = try_asset(&request, ".".as_ref()); } if !response.is_success() { - response = try_asset(&request, concat!(env!("CARGO_MANIFEST_DIR"), "/../../").as_ref()); + response = try_asset( + &request, + concat!(env!("CARGO_MANIFEST_DIR"), "/../../").as_ref(), + ); } // Make sure browsers don't cache anything (Chrome appeared to with this // header?)