From b4d62a45c2c0d697a0ce88f0686fe05a7cba4805 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Fri, 7 Mar 2025 15:27:14 +0100 Subject: [PATCH 01/14] feat(rust): do not yield to the host while guest tasks are ready (#1197) * feat(rust): do not yield to the host while guest tasks are ready Signed-off-by: Roman Volosatovs * Update crates/guest-rust/rt/src/async_support.rs --------- Signed-off-by: Roman Volosatovs Co-authored-by: Joel Dice --- crates/guest-rust/rt/src/async_support.rs | 38 ++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index b3d52f7da..e94398a18 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -2,6 +2,7 @@ #![allow(static_mut_refs)] extern crate std; +use core::sync::atomic::{AtomicBool, Ordering}; use std::alloc::{self, Layout}; use std::any::Any; use std::boxed::Box; @@ -78,26 +79,29 @@ pub fn with_entry(handle: u32, fun: impl FnOnce(hash_map::Entry<'_, u32, Hand fun(unsafe { HANDLES.entry(handle) }) } -fn dummy_waker() -> Waker { - struct DummyWaker; - - impl Wake for DummyWaker { - fn wake(self: Arc) {} - } - - static WAKER: Lazy> = Lazy::new(|| Arc::new(DummyWaker)); - - WAKER.clone().into() -} - /// Poll the specified task until it either completes or can't make immediate /// progress. unsafe fn poll(state: *mut FutureState) -> Poll<()> { + #[derive(Default)] + struct FutureWaker(AtomicBool); + + impl Wake for FutureWaker { + fn wake(self: Arc) { + Self::wake_by_ref(&self) + } + + fn wake_by_ref(self: &Arc) { + self.0.store(true, Ordering::Relaxed) + } + } + loop { if let Some(futures) = (*state).tasks.as_mut() { let old = CURRENT; CURRENT = state; - let poll = futures.poll_next_unpin(&mut Context::from_waker(&dummy_waker())); + let waker: Arc = Arc::default(); + let poll = + futures.poll_next_unpin(&mut Context::from_waker(&Arc::clone(&waker).into())); CURRENT = old; if SPAWNED.is_empty() { @@ -107,7 +111,13 @@ unsafe fn poll(state: *mut FutureState) -> Poll<()> { (*state).tasks = None; break Poll::Ready(()); } - Poll::Pending => break Poll::Pending, + Poll::Pending => { + // TODO: Return `CallbackCode.YIELD` (see https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#canon-lift) + // to the host before polling again once a host implementation exists to support it. + if !waker.0.load(Ordering::Relaxed) { + break Poll::Pending; + } + } } } else { futures.extend(SPAWNED.drain(..)); From 1d8cbb92b7aed06b6e762a7b49d3a4691b0280b8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 7 Mar 2025 12:35:25 -0600 Subject: [PATCH 02/14] Update support for p3 to match the latest wasm-tools (#1198) * Update wasm-tools dependencies * Fixes from wasm-tools update to p3 support Fix the name and signature of various intrinsics in the Rust bindings generator for p3 support. This is detected by updating wasm-tools in the wasip3-prototyping repository which requires changes here. * minimal stub of waitable-set support This is temporary (and relies on the host to ignore the waitable-set parameter) until we have proper waitable-set support, which I'll work on soon. Signed-off-by: Joel Dice --------- Signed-off-by: Joel Dice Co-authored-by: Joel Dice --- crates/core/src/abi.rs | 2 +- crates/guest-rust/rt/src/async_support.rs | 35 ++++++++++--------- .../rt/src/async_support/future_support.rs | 6 ++-- .../rt/src/async_support/stream_support.rs | 6 ++-- crates/rust/src/interface.rs | 26 ++++++-------- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index fe5973559..0eece4379 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -985,7 +985,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { assert_eq!(self.stack.len(), 2); self.emit(&Instruction::AsyncCallWasm { - name: &format!("[async]{}", func.name), + name: &format!("[async-lower]{}", func.name), size: params_size.size_wasm32(), align: params_align.align_wasm32(), }); diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index e94398a18..abf58a454 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -388,7 +388,7 @@ impl ErrorContext { { #[link(wasm_import_module = "$root")] extern "C" { - #[link_name = "[error-context-new;encoding=utf8]"] + #[link_name = "[error-context-new-utf8]"] fn context_new(_: *const u8, _: usize) -> i32; } @@ -427,7 +427,7 @@ impl ErrorContext { } #[link(wasm_import_module = "$root")] extern "C" { - #[link_name = "[error-context-debug-message;encoding=utf8;realloc=cabi_realloc]"] + #[link_name = "[error-context-debug-message-utf8]"] fn error_context_debug_message(_: u32, _: &mut RetPtr); } @@ -484,7 +484,7 @@ pub fn spawn(future: impl Future + 'static) { unsafe { SPAWNED.push(Box::pin(future)) } } -fn task_wait(state: &mut FutureState) { +fn waitable_set_wait(state: &mut FutureState) { #[cfg(not(target_arch = "wasm32"))] { _ = state; @@ -495,12 +495,13 @@ fn task_wait(state: &mut FutureState) { { #[link(wasm_import_module = "$root")] extern "C" { - #[link_name = "[task-wait]"] - fn wait(_: *mut i32) -> i32; + #[link_name = "[waitable-set-wait]"] + fn wait(_: u32, _: *mut i32) -> i32; } let mut payload = [0i32; 2]; unsafe { - let event0 = wait(payload.as_mut_ptr()); + // TODO: provide a real waitable-set here: + let event0 = wait(0, payload.as_mut_ptr()); callback(state as *mut _ as _, event0, payload[0], payload[1]); } } @@ -508,8 +509,8 @@ fn task_wait(state: &mut FutureState) { /// Run the specified future to completion, returning the result. /// -/// This uses `task.wait` to poll for progress on any in-progress calls to -/// async-lowered imports as necessary. +/// This uses `waitable-set.wait` to poll for progress on any in-progress calls +/// to async-lowered imports as necessary. // TODO: refactor so `'static` bounds aren't necessary pub fn block_on(future: impl Future + 'static) -> T { let (tx, mut rx) = oneshot::channel(); @@ -524,12 +525,12 @@ pub fn block_on(future: impl Future + 'static) -> T { loop { match unsafe { poll(state) } { Poll::Ready(()) => break rx.try_recv().unwrap().unwrap(), - Poll::Pending => task_wait(state), + Poll::Pending => waitable_set_wait(state), } } } -/// Call the `task.yield` canonical built-in function. +/// Call the `yield` canonical built-in function. /// /// This yields control to the host temporarily, allowing other tasks to make /// progress. It's a good idea to call this inside a busy loop which does not @@ -544,7 +545,7 @@ pub fn task_yield() { { #[link(wasm_import_module = "$root")] extern "C" { - #[link_name = "[task-yield]"] + #[link_name = "[yield]"] fn yield_(); } unsafe { @@ -553,12 +554,12 @@ pub fn task_yield() { } } -/// Call the `task.backpressure` canonical built-in function. +/// Call the `backpressure.set` canonical built-in function. /// /// When `enabled` is `true`, this tells the host to defer any new calls to this -/// component instance until further notice (i.e. until `task.backpressure` is +/// component instance until further notice (i.e. until `backpressure.set` is /// called again with `enabled` set to `false`). -pub fn task_backpressure(enabled: bool) { +pub fn backpressure_set(enabled: bool) { #[cfg(not(target_arch = "wasm32"))] { _ = enabled; @@ -569,11 +570,11 @@ pub fn task_backpressure(enabled: bool) { { #[link(wasm_import_module = "$root")] extern "C" { - #[link_name = "[task-backpressure]"] - fn backpressure(_: i32); + #[link_name = "[backpressure-set]"] + fn backpressure_set(_: i32); } unsafe { - backpressure(if enabled { 1 } else { 0 }); + backpressure_set(if enabled { 1 } else { 0 }); } } } diff --git a/crates/guest-rust/rt/src/async_support/future_support.rs b/crates/guest-rust/rt/src/async_support/future_support.rs index aa6e12f63..5e1aedce6 100644 --- a/crates/guest-rust/rt/src/async_support/future_support.rs +++ b/crates/guest-rust/rt/src/async_support/future_support.rs @@ -25,7 +25,7 @@ pub struct FutureVtable { pub cancel_write: unsafe extern "C" fn(future: u32) -> u32, pub cancel_read: unsafe extern "C" fn(future: u32) -> u32, pub close_writable: unsafe extern "C" fn(future: u32, err_ctx: u32), - pub close_readable: unsafe extern "C" fn(future: u32), + pub close_readable: unsafe extern "C" fn(future: u32, err_ctx: u32), pub new: unsafe extern "C" fn() -> u32, } @@ -418,7 +418,9 @@ impl Drop for FutureReader { } Handle::Read | Handle::LocalClosed => unsafe { entry.remove(); - (self.vtable.close_readable)(handle); + // TODO: expose `0` here as an error context in the + // API (or auto-fill-in? unsure). + (self.vtable.close_readable)(handle, 0); }, Handle::Write | Handle::WriteClosedErr(_) => unreachable!(), }, diff --git a/crates/guest-rust/rt/src/async_support/stream_support.rs b/crates/guest-rust/rt/src/async_support/stream_support.rs index 3a52bd73a..fd9bb62d3 100644 --- a/crates/guest-rust/rt/src/async_support/stream_support.rs +++ b/crates/guest-rust/rt/src/async_support/stream_support.rs @@ -38,7 +38,7 @@ pub struct StreamVtable { pub cancel_write: unsafe extern "C" fn(future: u32) -> u32, pub cancel_read: unsafe extern "C" fn(future: u32) -> u32, pub close_writable: unsafe extern "C" fn(future: u32, err_ctx: u32), - pub close_readable: unsafe extern "C" fn(future: u32), + pub close_readable: unsafe extern "C" fn(future: u32, err_ctx: u32), pub new: unsafe extern "C" fn() -> u32, } @@ -487,7 +487,9 @@ impl Drop for StreamReader { } Handle::Read | Handle::LocalClosed => unsafe { entry.remove(); - (self.vtable.close_readable)(handle); + // TODO: expose `0` here as an error context in the + // API (or auto-fill-in? unsure). + (self.vtable.close_readable)(handle, 0); }, Handle::Write | Handle::WriteClosedErr(_) => unreachable!(), }, diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index acdabfdad..87ff5533a 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -578,7 +578,7 @@ pub mod vtable{ordinal} {{ #[cfg(not(target_arch = "wasm32"))] unsafe extern "C" fn close_writable(_: u32, _: u32) {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn close_readable(_: u32) {{ unreachable!() }} + unsafe extern "C" fn close_readable(_: u32, _: u32) {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] unsafe extern "C" fn new() -> u32 {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] @@ -598,12 +598,10 @@ pub mod vtable{ordinal} {{ #[link_name = "[future-close-writable-{index}]{func_name}"] fn close_writable(_: u32, _: u32); #[link_name = "[future-close-readable-{index}]{func_name}"] - fn close_readable(_: u32); - #[link_name = "[future-new-{index}]{func_name}"] - fn new() -> u32; - #[link_name = "[async][future-read-{index}]{func_name}"] + fn close_readable(_: u32, _: u32); + #[link_name = "[async-lower][future-read-{index}]{func_name}"] fn start_read(_: u32, _: *mut u8) -> u32; - #[link_name = "[async][future-write-{index}]{func_name}"] + #[link_name = "[async-lower][future-write-{index}]{func_name}"] fn start_write(_: u32, _: *mut u8) -> u32; }} @@ -762,7 +760,7 @@ pub mod vtable{ordinal} {{ #[cfg(not(target_arch = "wasm32"))] unsafe extern "C" fn close_writable(_: u32, _: u32) {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn close_readable(_: u32) {{ unreachable!() }} + unsafe extern "C" fn close_readable(_: u32, _: u32) {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] unsafe extern "C" fn new() -> u32 {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] @@ -782,12 +780,10 @@ pub mod vtable{ordinal} {{ #[link_name = "[stream-close-writable-{index}]{func_name}"] fn close_writable(_: u32, _: u32); #[link_name = "[stream-close-readable-{index}]{func_name}"] - fn close_readable(_: u32); - #[link_name = "[stream-new-{index}]{func_name}"] - fn new() -> u32; - #[link_name = "[async][stream-read-{index}]{func_name}"] + fn close_readable(_: u32, _: u32); + #[link_name = "[async-lower][stream-read-{index}]{func_name}"] fn start_read(_: u32, _: *mut u8, _: u32) -> u32; - #[link_name = "[async][stream-write-{index}]{func_name}"] + #[link_name = "[async-lower][stream-write-{index}]{func_name}"] fn start_write(_: u32, _: *mut u8, _: u32) -> u32; }} @@ -817,7 +813,7 @@ pub mod vtable{ordinal} {{ return; } - self.generate_payloads("[import-payload]", func, interface); + self.generate_payloads("", func, interface); let async_ = match &self.gen.opts.async_ { AsyncConfig::None => false, @@ -924,7 +920,7 @@ pub mod vtable{ordinal} {{ ) { let name_snake = func.name.to_snake_case().replace('.', "_"); - self.generate_payloads("[export-payload]", func, interface); + self.generate_payloads("[export]", func, interface); uwrite!( self.src, @@ -1051,7 +1047,7 @@ pub mod vtable{ordinal} {{ let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); let export_name = func.legacy_core_export_name(wasm_module_export_name.as_deref()); let export_name = if async_ { - format!("[async]{export_name}") + format!("[async-lift]{export_name}") } else { export_name.to_string() }; From 88374b41d0911acd239f4f4ec738678bed89e6e8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 7 Mar 2025 17:16:02 -0600 Subject: [PATCH 03/14] Store arguments for async params/results on the stack (#1185) After #1176 there's no longer any need to store these values on the heap, so store them on the stack instead. This also updates to store params/results in an overlapping allocation which should be reasonable as it's local per-import and results are never written before parameters are read. (and params are never read after results are written). --- crates/core/src/abi.rs | 98 ++++++++--------------- crates/csharp/src/function.rs | 3 +- crates/guest-rust/rt/src/async_support.rs | 8 +- crates/moonbit/src/lib.rs | 3 +- crates/rust/src/bindgen.rs | 35 +++----- crates/teavm-java/src/lib.rs | 3 +- 6 files changed, 51 insertions(+), 99 deletions(-) diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 0eece4379..9be5533df 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -551,19 +551,8 @@ def_instruction! { blocks: usize, } : [1] => [0], - /// Allocate the parameter and/or return areas to use for an - /// async-lowered import call. - /// - /// This cannot be allocated on the (shadow-)stack since it needs to - /// remain valid until the callee has finished using the buffers, which - /// may be after we pop the current stack frame. - AsyncMalloc { size: usize, align: usize } : [0] => [1], - /// Call an async-lowered import. - /// - /// `size` and `align` are used to deallocate the parameter area - /// allocated using `AsyncMalloc` after the callee task returns a value. - AsyncCallWasm { name: &'a str, size: usize, align: usize } : [2] => [0], + AsyncCallWasm { name: &'a str } : [2] => [0], /// Generate code to run after `CallInterface` for an async-lifted export. /// @@ -913,18 +902,15 @@ impl<'a, B: Bindgen> Generator<'a, B> { self_.stack.push(ptr); }; - let params_size_align = if self.async_ { + if self.async_ { let ElementInfo { size, align } = self .bindgen .sizes() .record(func.params.iter().map(|(_, ty)| ty)); - self.emit(&Instruction::AsyncMalloc { - size: size.size_wasm32(), - align: align.align_wasm32(), - }); - let ptr = self.stack.pop().unwrap(); + let ptr = self + .bindgen + .return_pointer(size.size_wasm32(), align.align_wasm32()); lower_to_memory(self, ptr); - Some((size, align)) } else { if !sig.indirect_params { // If the parameters for this function aren't indirect @@ -966,47 +952,39 @@ impl<'a, B: Bindgen> Generator<'a, B> { }; lower_to_memory(self, ptr); } - None - }; + } - // If necessary we may need to prepare a return pointer for - // this ABI. - let dealloc_size_align = - if let Some((params_size, params_align)) = params_size_align { - let ElementInfo { size, align } = - self.bindgen.sizes().record(func.result.iter()); - self.emit(&Instruction::AsyncMalloc { - size: size.size_wasm32(), - align: align.align_wasm32(), - }); - let ptr = self.stack.pop().unwrap(); + if self.async_ { + let ElementInfo { size, align } = + self.bindgen.sizes().record(func.result.iter()); + let ptr = self + .bindgen + .return_pointer(size.size_wasm32(), align.align_wasm32()); + self.return_pointer = Some(ptr.clone()); + self.stack.push(ptr); + + assert_eq!(self.stack.len(), 2); + self.emit(&Instruction::AsyncCallWasm { + name: &format!("[async-lower]{}", func.name), + }); + } else { + // If necessary we may need to prepare a return pointer for + // this ABI. + if self.variant == AbiVariant::GuestImport && sig.retptr { + let info = self.bindgen.sizes().params(&func.result); + let ptr = self + .bindgen + .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()); self.return_pointer = Some(ptr.clone()); self.stack.push(ptr); + } - assert_eq!(self.stack.len(), 2); - self.emit(&Instruction::AsyncCallWasm { - name: &format!("[async-lower]{}", func.name), - size: params_size.size_wasm32(), - align: params_align.align_wasm32(), - }); - Some((size, align)) - } else { - if self.variant == AbiVariant::GuestImport && sig.retptr { - let info = self.bindgen.sizes().params(&func.result); - let ptr = self - .bindgen - .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()); - self.return_pointer = Some(ptr.clone()); - self.stack.push(ptr); - } - - assert_eq!(self.stack.len(), sig.params.len()); - self.emit(&Instruction::CallWasm { - name: &func.name, - sig: &sig, - }); - None - }; + assert_eq!(self.stack.len(), sig.params.len()); + self.emit(&Instruction::CallWasm { + name: &func.name, + sig: &sig, + }); + } if !(sig.retptr || self.async_) { // With no return pointer in use we can simply lift the @@ -1043,14 +1021,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&Instruction::Flush { amt: usize::from(func.result.is_some()), }); - - if let Some((size, align)) = dealloc_size_align { - self.stack.push(ptr); - self.emit(&Instruction::GuestDeallocate { - size: size.size_wasm32(), - align: align.align_wasm32(), - }); - } } self.emit(&Instruction::Return { diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index aa3a6a6c7..383ba196c 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -1253,8 +1253,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.extend(operands.iter().take(*amt).map(|v| v.clone())); } - Instruction::AsyncMalloc { .. } - | Instruction::AsyncPostCallInterface { .. } + Instruction::AsyncPostCallInterface { .. } | Instruction::AsyncCallReturn { .. } | Instruction::FutureLower { .. } | Instruction::FutureLift { .. } diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index abf58a454..6f3eea0b7 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -3,7 +3,6 @@ extern crate std; use core::sync::atomic::{AtomicBool, Ordering}; -use std::alloc::{self, Layout}; use std::any::Any; use std::boxed::Box; use std::collections::{hash_map, HashMap}; @@ -157,7 +156,6 @@ pub fn first_poll( #[doc(hidden)] pub async unsafe fn await_result( import: unsafe extern "C" fn(*mut u8, *mut u8) -> i32, - params_layout: Layout, params: *mut u8, results: *mut u8, ) { @@ -182,17 +180,13 @@ pub async unsafe fn await_result( let (tx, rx) = oneshot::channel(); CALLS.insert(call, tx); rx.await.unwrap(); - alloc::dealloc(params, params_layout); } STATUS_STARTED => { - alloc::dealloc(params, params_layout); let (tx, rx) = oneshot::channel(); CALLS.insert(call, tx); rx.await.unwrap(); } - STATUS_RETURNED | STATUS_DONE => { - alloc::dealloc(params, params_layout); - } + STATUS_RETURNED | STATUS_DONE => {} _ => unreachable!("unrecognized async call status"), } diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index dc1ffcf8a..449eb826d 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -2665,8 +2665,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.extend(operands.iter().take(*amt).map(|v| v.clone())); } - Instruction::AsyncMalloc { .. } - | Instruction::AsyncPostCallInterface { .. } + Instruction::AsyncPostCallInterface { .. } | Instruction::AsyncCallReturn { .. } | Instruction::FutureLower { .. } | Instruction::FutureLift { .. } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index a25c6d4ce..b3d15de3e 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -278,7 +278,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { // stack whereas exports use a per-module return area to cut down on // stack usage. Note that for imports this also facilitates "adapter // modules" for components to not have data segments. - if self.gen.in_import { + if size == 0 { + // If the size requested is 0 then we know it won't be written to so + // hand out a null pointer. This can happen with async for example + // when the params or results are zero-sized. + uwrite!(self.src, "let ptr{tmp} = core::ptr::null_mut::();"); + } else if self.gen.in_import { + // Import return areas are stored on the stack since this stack + // frame will be around for the entire function call. self.import_return_pointer_area_size = self.import_return_pointer_area_size.max(size); self.import_return_pointer_area_align = self.import_return_pointer_area_align.max(align); @@ -287,6 +294,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { "let ptr{tmp} = ret_area.0.as_mut_ptr().cast::();" ); } else { + // Export return areas are stored in `static` memory as they need to + // persist beyond the function call itself (and are cleaned-up in + // `post-return`). self.gen.return_pointer_area_size = self.gen.return_pointer_area_size.max(size); self.gen.return_pointer_area_align = self.gen.return_pointer_area_align.max(align); uwriteln!( @@ -870,20 +880,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(");\n"); } - Instruction::AsyncCallWasm { name, size, align } => { + Instruction::AsyncCallWasm { name, .. } => { let func = self.declare_import(name, &[WasmType::Pointer; 2], &[WasmType::I32]); let async_support = self.gen.gen.async_support_path(); - let tmp = self.tmp(); - let layout = format!("layout{tmp}"); - let alloc = self.gen.path_to_std_alloc_module(); - self.push_str(&format!( - "let {layout} = {alloc}::Layout::from_size_align_unchecked({size}, {align});\n", - )); let operands = operands.join(", "); uwriteln!( self.src, - "{async_support}::await_result({func}, {layout}, {operands}).await;" + "{async_support}::await_result({func}, {operands}).await;" ); } @@ -951,19 +955,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(";\n"); } - Instruction::AsyncMalloc { size, align } => { - let alloc = self.gen.path_to_std_alloc_module(); - let tmp = self.tmp(); - let ptr = format!("ptr{tmp}"); - let layout = format!("layout{tmp}"); - uwriteln!( - self.src, - "let {layout} = {alloc}::Layout::from_size_align_unchecked({size}, {align}); - let {ptr} = {alloc}::alloc({layout});" - ); - results.push(ptr); - } - Instruction::AsyncPostCallInterface { func } => { let result = &operands[0]; self.async_result_name = Some(result.clone()); diff --git a/crates/teavm-java/src/lib.rs b/crates/teavm-java/src/lib.rs index 2ea421749..681537178 100644 --- a/crates/teavm-java/src/lib.rs +++ b/crates/teavm-java/src/lib.rs @@ -1999,8 +1999,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.extend(operands.iter().take(*amt).map(|v| v.clone())); } - Instruction::AsyncMalloc { .. } - | Instruction::AsyncPostCallInterface { .. } + Instruction::AsyncPostCallInterface { .. } | Instruction::AsyncCallReturn { .. } | Instruction::FutureLower { .. } | Instruction::FutureLift { .. } From d73c073b0f277db55e4994397931ee00daf47e20 Mon Sep 17 00:00:00 2001 From: Colin D Murphy Date: Mon, 10 Mar 2025 14:54:31 -0400 Subject: [PATCH 04/14] feat: Allow variants and records to be ignored by additional_derives (#1199) This feature allows some variants and records to use types for which adding traits will cause compilation to fail, such as serde::Deserialize on wasi:io/streams. Variants and records are specified as they are listed in the wit file, i.e. in kebab case. --- crates/guest-rust/macro/src/lib.rs | 13 ++++++++++++ crates/rust/src/interface.rs | 32 +++++++++++++++++++++++------- crates/rust/src/lib.rs | 16 +++++++++++++++ crates/rust/tests/codegen.rs | 21 +++++++++++++++++++- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 24a04f291..b4be3798c 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -115,6 +115,10 @@ impl Parse for Config { .map(|p| p.into_token_stream().to_string()) .collect() } + Opt::AdditionalDerivesIgnore(list) => { + opts.additional_derive_ignore = + list.into_iter().map(|i| i.value()).collect() + } Opt::With(with) => opts.with.extend(with), Opt::GenerateAll => { opts.generate_all = true; @@ -323,6 +327,7 @@ mod kw { syn::custom_keyword!(stubs); syn::custom_keyword!(export_prefix); syn::custom_keyword!(additional_derives); + syn::custom_keyword!(additional_derives_ignore); syn::custom_keyword!(with); syn::custom_keyword!(generate_all); syn::custom_keyword!(type_section_suffix); @@ -383,6 +388,7 @@ enum Opt { ExportPrefix(syn::LitStr), // Parse as paths so we can take the concrete types/macro names rather than raw strings AdditionalDerives(Vec), + AdditionalDerivesIgnore(Vec), With(HashMap), GenerateAll, TypeSectionSuffix(syn::LitStr), @@ -496,6 +502,13 @@ impl Parse for Opt { syn::bracketed!(contents in input); let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?; Ok(Opt::AdditionalDerives(list.iter().cloned().collect())) + } else if l.peek(kw::additional_derives_ignore) { + input.parse::()?; + input.parse::()?; + let contents; + syn::bracketed!(contents in input); + let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?; + Ok(Opt::AdditionalDerivesIgnore(list.iter().cloned().collect())) } else if l.peek(kw::with) { input.parse::()?; input.parse::()?; diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 87ff5533a..f99fe1eeb 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1821,7 +1821,15 @@ pub mod vtable{ordinal} {{ .collect(); for (name, mode) in self.modes_of(id) { self.rustdoc(docs); - let mut derives = additional_derives.clone(); + let mut derives = BTreeSet::new(); + if !self + .gen + .opts + .additional_derive_ignore + .contains(&name.to_kebab_case()) + { + derives.extend(additional_derives.clone()); + } if info.is_copy() { self.push_str("#[repr(C)]\n"); derives.extend(["Copy", "Clone"].into_iter().map(|s| s.to_string())); @@ -1924,7 +1932,15 @@ pub mod vtable{ordinal} {{ .collect(); for (name, mode) in self.modes_of(id) { self.rustdoc(docs); - let mut derives = additional_derives.clone(); + let mut derives = BTreeSet::new(); + if !self + .gen + .opts + .additional_derive_ignore + .contains(&name.to_kebab_case()) + { + derives.extend(additional_derives.clone()); + } if info.is_copy() { derives.extend(["Copy", "Clone"].into_iter().map(|s| s.to_string())); } else if info.is_clone() { @@ -2072,13 +2088,15 @@ pub mod vtable{ordinal} {{ self.int_repr(enum_.tag()); self.push_str(")]\n"); // We use a BTree set to make sure we don't have any duplicates and a stable order - let mut derives: BTreeSet = self + let mut derives: BTreeSet = BTreeSet::new(); + if !self .gen .opts - .additional_derive_attributes - .iter() - .cloned() - .collect(); + .additional_derive_ignore + .contains(&name.to_kebab_case()) + { + derives.extend(self.gen.opts.additional_derive_attributes.to_vec()); + } derives.extend( ["Clone", "Copy", "PartialEq", "Eq", "PartialOrd", "Ord"] .into_iter() diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 0807f11e5..5fabffdc7 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -248,6 +248,15 @@ pub struct Opts { #[cfg_attr(feature = "clap", arg(long = "additional_derive_attribute", short = 'd', default_values_t = Vec::::new()))] pub additional_derive_attributes: Vec, + /// Variants and records to ignore when applying additional derive attributes. + /// + /// These names are specified as they are listed in the wit file, i.e. in kebab case. + /// This feature allows some variants and records to use types for which adding traits will cause + /// compilation to fail, such as serde::Deserialize on wasi:io/streams. + /// + #[cfg_attr(feature = "clap", arg(long = "additional_derive_ignore", default_values_t = Vec::::new()))] + pub additional_derive_ignore: Vec, + /// Remapping of wit interface and type names to Rust module names and types. /// /// Argument must be of the form `k=v` and this option can be passed @@ -1030,6 +1039,13 @@ impl WorldGenerator for RustWasm { self.opts.additional_derive_attributes ); } + if !self.opts.additional_derive_ignore.is_empty() { + uwriteln!( + self.src_preamble, + "// * additional derives ignored {:?}", + self.opts.additional_derive_ignore + ); + } for (k, v) in self.opts.with.iter() { uwriteln!(self.src_preamble, "// * with {k:?} = {v}"); } diff --git a/crates/rust/tests/codegen.rs b/crates/rust/tests/codegen.rs index 6568c65b6..46b2379df 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -388,13 +388,26 @@ mod custom_derives { inline: " package my:inline; + interface blag { + resource input-stream { + read: func(len: u64) -> list; + } + } + interface blah { + use blag.{input-stream}; record foo { field1: string, field2: list } bar: func(cool: foo); + + variant ignoreme { + stream-type(input-stream), + } + + barry: func(warm: ignoreme); } world baz { @@ -405,9 +418,10 @@ mod custom_derives { // Clone is included by default almost everywhere, so include it here to make sure it // doesn't conflict additional_derives: [serde::Serialize, serde::Deserialize, Hash, Clone, PartialEq, Eq], + additional_derives_ignore: ["ignoreme"], }); - use exports::my::inline::blah::Foo; + use exports::my::inline::blah::{Foo, Ignoreme}; struct Component; @@ -423,6 +437,11 @@ mod custom_derives { // compilation will fail here let _ = serde_json::to_string(&cool); } + + fn barry(warm: Ignoreme) { + // Compilation would fail if serde::Deserialize was applied to Ignoreme + let _ = warm; + } } export!(Component); From 0e191cc97d2ebbad56168ff4ad51c5fe4e4528ae Mon Sep 17 00:00:00 2001 From: Christof Petig <33882057+cpetig@users.noreply.github.com> Date: Tue, 11 Mar 2025 17:00:49 +0100 Subject: [PATCH 05/14] Automatic adaption to 64bit architectures in guest code (#1163) * Automatic adaption to 64bit architectures in guest code * prefer absolute path for core crate * fix rust codegen after merge --- crates/c/src/lib.rs | 67 +++++++---- crates/core/src/abi.rs | 217 ++++++++++++++++------------------ crates/csharp/src/function.rs | 59 +++++---- crates/moonbit/src/lib.rs | 70 +++++++---- crates/rust/src/bindgen.rs | 122 +++++++++++++------ crates/rust/src/interface.rs | 40 ++++--- crates/rust/src/lib.rs | 4 +- crates/teavm-java/src/lib.rs | 76 +++++++----- 8 files changed, 386 insertions(+), 269 deletions(-) diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 424cb0e8f..6d1ae0671 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -18,8 +18,8 @@ struct C { opts: Opts, h_includes: Vec, c_includes: Vec, - return_pointer_area_size: usize, - return_pointer_area_align: usize, + return_pointer_area_size: ArchitectureSize, + return_pointer_area_align: Alignment, names: Ns, needs_string: bool, needs_union_int32_float: bool, @@ -463,7 +463,7 @@ impl WorldGenerator for C { // Declare a statically-allocated return area, if needed. We only do // this for export bindings, because import bindings allocate their // return-area on the stack. - if self.return_pointer_area_size > 0 { + if !self.return_pointer_area_size.is_empty() { // Automatic indentation avoided due to `extern "C" {` declaration uwrite!( c_str, @@ -471,8 +471,10 @@ impl WorldGenerator for C { __attribute__((__aligned__({}))) static uint8_t RET_AREA[{}]; ", - self.return_pointer_area_align, - self.return_pointer_area_size, + self.return_pointer_area_align + .format(POINTER_SIZE_EXPRESSION), + self.return_pointer_area_size + .format(POINTER_SIZE_EXPRESSION), ); } c_str.push_str(&self.src.c_adapters); @@ -1779,12 +1781,14 @@ impl InterfaceGenerator<'_> { .. } = f; - if import_return_pointer_area_size > 0 { + if !import_return_pointer_area_size.is_empty() { self.src.c_adapters(&format!( "\ - __attribute__((__aligned__({import_return_pointer_area_align}))) - uint8_t ret_area[{import_return_pointer_area_size}]; + __attribute__((__aligned__({}))) + uint8_t ret_area[{}]; ", + import_return_pointer_area_align.format(POINTER_SIZE_EXPRESSION), + import_return_pointer_area_size.format(POINTER_SIZE_EXPRESSION), )); } @@ -2121,8 +2125,8 @@ struct FunctionBindgen<'a, 'b> { params: Vec, wasm_return: Option, ret_store_cnt: usize, - import_return_pointer_area_size: usize, - import_return_pointer_area_align: usize, + import_return_pointer_area_size: ArchitectureSize, + import_return_pointer_area_align: Alignment, /// Borrows observed during lifting an export, that will need to be dropped when the guest /// function exits. @@ -2150,8 +2154,8 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { params: Vec::new(), wasm_return: None, ret_store_cnt: 0, - import_return_pointer_area_size: 0, - import_return_pointer_area_align: 0, + import_return_pointer_area_size: Default::default(), + import_return_pointer_area_align: Default::default(), borrow_decls: Default::default(), borrows: Vec::new(), } @@ -2164,23 +2168,40 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { self.src.push_str(";\n"); } - fn load(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec) { - results.push(format!("*(({}*) ({} + {}))", ty, operands[0], offset)); + fn load( + &mut self, + ty: &str, + offset: ArchitectureSize, + operands: &[String], + results: &mut Vec, + ) { + results.push(format!( + "*(({}*) ({} + {}))", + ty, + operands[0], + offset.format(POINTER_SIZE_EXPRESSION) + )); } - fn load_ext(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec) { + fn load_ext( + &mut self, + ty: &str, + offset: ArchitectureSize, + operands: &[String], + results: &mut Vec, + ) { self.load(ty, offset, operands, results); let result = results.pop().unwrap(); results.push(format!("(int32_t) {}", result)); } - fn store(&mut self, ty: &str, offset: i32, operands: &[String]) { + fn store(&mut self, ty: &str, offset: ArchitectureSize, operands: &[String]) { uwriteln!( self.src, "*(({}*)({} + {})) = {};", ty, operands[1], - offset, + offset.format(POINTER_SIZE_EXPRESSION), operands[0] ); } @@ -2230,7 +2251,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.blocks.push((src.into(), mem::take(operands))); } - fn return_pointer(&mut self, size: usize, align: usize) -> String { + fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { let ptr = self.locals.tmp("ptr"); // Use a stack-based return area for imports, because exports need @@ -3034,8 +3055,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "uint8_t *{ptr} = {};", operands[0]); let i = self.locals.tmp("i"); uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{"); - let size = self.gen.gen.sizes.size(element).size_wasm32(); - uwriteln!(self.src, "uint8_t *base = {ptr} + {i} * {size};"); + let size = self.gen.gen.sizes.size(element); + uwriteln!( + self.src, + "uint8_t *base = {ptr} + {i} * {};", + size.format(POINTER_SIZE_EXPRESSION) + ); uwriteln!(self.src, "(void) base;"); uwrite!(self.src, "{body}"); uwriteln!(self.src, "}}"); @@ -3272,3 +3297,5 @@ pub fn to_c_ident(name: &str) -> String { s => s.to_snake_case(), } } + +const POINTER_SIZE_EXPRESSION: &str = "sizeof(void*)"; diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 9be5533df..ebb620b6c 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -1,7 +1,7 @@ pub use wit_parser::abi::{AbiVariant, WasmSignature, WasmType}; use wit_parser::{ - ElementInfo, Enum, Flags, FlagsRepr, Function, Handle, Int, Record, Resolve, Result_, - SizeAlign, Tuple, Type, TypeDefKind, TypeId, Variant, + align_to_arch, Alignment, ArchitectureSize, ElementInfo, Enum, Flags, FlagsRepr, Function, + Handle, Int, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeDefKind, TypeId, Variant, }; // Helper macro for defining instructions without having to have tons of @@ -86,67 +86,67 @@ def_instruction! { /// Pops a pointer from the stack and loads a little-endian `i32` from /// it, using the specified constant offset. - I32Load { offset: i32 } : [1] => [1], + I32Load { offset: ArchitectureSize } : [1] => [1], /// Pops a pointer from the stack and loads a little-endian `i8` from /// it, using the specified constant offset. The value loaded is the /// zero-extended to 32-bits - I32Load8U { offset: i32 } : [1] => [1], + I32Load8U { offset: ArchitectureSize } : [1] => [1], /// Pops a pointer from the stack and loads a little-endian `i8` from /// it, using the specified constant offset. The value loaded is the /// sign-extended to 32-bits - I32Load8S { offset: i32 } : [1] => [1], + I32Load8S { offset: ArchitectureSize } : [1] => [1], /// Pops a pointer from the stack and loads a little-endian `i16` from /// it, using the specified constant offset. The value loaded is the /// zero-extended to 32-bits - I32Load16U { offset: i32 } : [1] => [1], + I32Load16U { offset: ArchitectureSize } : [1] => [1], /// Pops a pointer from the stack and loads a little-endian `i16` from /// it, using the specified constant offset. The value loaded is the /// sign-extended to 32-bits - I32Load16S { offset: i32 } : [1] => [1], + I32Load16S { offset: ArchitectureSize } : [1] => [1], /// Pops a pointer from the stack and loads a little-endian `i64` from /// it, using the specified constant offset. - I64Load { offset: i32 } : [1] => [1], + I64Load { offset: ArchitectureSize } : [1] => [1], /// Pops a pointer from the stack and loads a little-endian `f32` from /// it, using the specified constant offset. - F32Load { offset: i32 } : [1] => [1], + F32Load { offset: ArchitectureSize } : [1] => [1], /// Pops a pointer from the stack and loads a little-endian `f64` from /// it, using the specified constant offset. - F64Load { offset: i32 } : [1] => [1], + F64Load { offset: ArchitectureSize } : [1] => [1], /// Like `I32Load` or `I64Load`, but for loading pointer values. - PointerLoad { offset: i32 } : [1] => [1], + PointerLoad { offset: ArchitectureSize } : [1] => [1], /// Like `I32Load` or `I64Load`, but for loading array length values. - LengthLoad { offset: i32 } : [1] => [1], + LengthLoad { offset: ArchitectureSize } : [1] => [1], /// Pops a pointer from the stack and then an `i32` value. /// Stores the value in little-endian at the pointer specified plus the /// constant `offset`. - I32Store { offset: i32 } : [2] => [0], + I32Store { offset: ArchitectureSize } : [2] => [0], /// Pops a pointer from the stack and then an `i32` value. /// Stores the low 8 bits of the value in little-endian at the pointer /// specified plus the constant `offset`. - I32Store8 { offset: i32 } : [2] => [0], + I32Store8 { offset: ArchitectureSize } : [2] => [0], /// Pops a pointer from the stack and then an `i32` value. /// Stores the low 16 bits of the value in little-endian at the pointer /// specified plus the constant `offset`. - I32Store16 { offset: i32 } : [2] => [0], + I32Store16 { offset: ArchitectureSize } : [2] => [0], /// Pops a pointer from the stack and then an `i64` value. /// Stores the value in little-endian at the pointer specified plus the /// constant `offset`. - I64Store { offset: i32 } : [2] => [0], + I64Store { offset: ArchitectureSize } : [2] => [0], /// Pops a pointer from the stack and then an `f32` value. /// Stores the value in little-endian at the pointer specified plus the /// constant `offset`. - F32Store { offset: i32 } : [2] => [0], + F32Store { offset: ArchitectureSize } : [2] => [0], /// Pops a pointer from the stack and then an `f64` value. /// Stores the value in little-endian at the pointer specified plus the /// constant `offset`. - F64Store { offset: i32 } : [2] => [0], + F64Store { offset: ArchitectureSize } : [2] => [0], /// Like `I32Store` or `I64Store`, but for storing pointer values. - PointerStore { offset: i32 } : [2] => [0], + PointerStore { offset: ArchitectureSize } : [2] => [0], /// Like `I32Store` or `I64Store`, but for storing array length values. - LengthStore { offset: i32 } : [2] => [0], + LengthStore { offset: ArchitectureSize } : [2] => [0], // Scalar lifting/lowering @@ -513,8 +513,8 @@ def_instruction! { /// Pushes the returned pointer onto the stack. Malloc { realloc: &'static str, - size: usize, - align: usize, + size: ArchitectureSize, + align: Alignment, } : [0] => [1], /// Used exclusively for guest-code generation this indicates that @@ -523,8 +523,8 @@ def_instruction! { /// /// This will pop a pointer from the stack and push nothing. GuestDeallocate { - size: usize, - align: usize, + size: ArchitectureSize, + align: Alignment, } : [1] => [0], /// Used exclusively for guest-code generation this indicates that @@ -678,7 +678,7 @@ pub trait Bindgen { /// Gets a operand reference to the return pointer area. /// /// The provided size and alignment is for the function's return type. - fn return_pointer(&mut self, size: usize, align: usize) -> Self::Operand; + fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> Self::Operand; /// Enters a new block of code to generate code for. /// @@ -757,7 +757,7 @@ pub fn lower_to_memory( true, ); generator.stack.push(value); - generator.write_to_memory(ty, address, 0); + generator.write_to_memory(ty, address, Default::default()); } pub fn lift_from_memory( @@ -774,7 +774,7 @@ pub fn lift_from_memory( bindgen, true, ); - generator.read_from_memory(ty, address, 0); + generator.read_from_memory(ty, address, Default::default()); generator.stack.pop().unwrap() } @@ -891,12 +891,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { } let lower_to_memory = |self_: &mut Self, ptr: B::Operand| { - let mut offset = 0usize; + let mut offset = ArchitectureSize::default(); for (nth, (_, ty)) in func.params.iter().enumerate() { self_.emit(&Instruction::GetArg { nth }); - offset = align_to(offset, self_.bindgen.sizes().align(ty).align_wasm32()); - self_.write_to_memory(ty, ptr.clone(), offset as i32); - offset += self_.bindgen.sizes().size(ty).size_wasm32(); + offset = align_to_arch(offset, self_.bindgen.sizes().align(ty)); + self_.write_to_memory(ty, ptr.clone(), offset); + offset += self_.bindgen.sizes().size(ty); } self_.stack.push(ptr); @@ -907,9 +907,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { .bindgen .sizes() .record(func.params.iter().map(|(_, ty)| ty)); - let ptr = self - .bindgen - .return_pointer(size.size_wasm32(), align.align_wasm32()); + let ptr = self.bindgen.return_pointer(size, align); lower_to_memory(self, ptr); } else { if !sig.indirect_params { @@ -924,23 +922,21 @@ impl<'a, B: Bindgen> Generator<'a, B> { // ... otherwise if parameters are indirect space is // allocated from them and each argument is lowered // individually into memory. - let info = self + let ElementInfo { size, align } = self .bindgen .sizes() .record(func.params.iter().map(|t| &t.1)); let ptr = match self.variant { // When a wasm module calls an import it will provide // space that isn't explicitly deallocated. - AbiVariant::GuestImport => self - .bindgen - .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()), + AbiVariant::GuestImport => self.bindgen.return_pointer(size, align), // When calling a wasm module from the outside, though, // malloc needs to be called. AbiVariant::GuestExport => { self.emit(&Instruction::Malloc { realloc: "cabi_realloc", - size: info.size.size_wasm32(), - align: info.align.align_wasm32(), + size, + align, }); self.stack.pop().unwrap() } @@ -957,9 +953,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { if self.async_ { let ElementInfo { size, align } = self.bindgen.sizes().record(func.result.iter()); - let ptr = self - .bindgen - .return_pointer(size.size_wasm32(), align.align_wasm32()); + let ptr = self.bindgen.return_pointer(size, align); self.return_pointer = Some(ptr.clone()); self.stack.push(ptr); @@ -972,9 +966,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // this ABI. if self.variant == AbiVariant::GuestImport && sig.retptr { let info = self.bindgen.sizes().params(&func.result); - let ptr = self - .bindgen - .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()); + let ptr = self.bindgen.return_pointer(info.size, info.align); self.return_pointer = Some(ptr.clone()); self.stack.push(ptr); } @@ -1017,7 +1009,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { } }; - self.read_results_from_memory(&func.result, ptr.clone(), 0); + self.read_results_from_memory( + &func.result, + ptr.clone(), + ArchitectureSize::default(), + ); self.emit(&Instruction::Flush { amt: usize::from(func.result.is_some()), }); @@ -1034,12 +1030,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { } let read_from_memory = |self_: &mut Self| { - let mut offset = 0usize; + let mut offset = ArchitectureSize::default(); let ptr = self_.stack.pop().unwrap(); for (_, ty) in func.params.iter() { - offset = align_to(offset, self_.bindgen.sizes().align(ty).align_wasm32()); - self_.read_from_memory(ty, ptr.clone(), offset as i32); - offset += self_.bindgen.sizes().size(ty).size_wasm32(); + offset = align_to_arch(offset, self_.bindgen.sizes().align(ty)); + self_.read_from_memory(ty, ptr.clone(), offset); + offset += self_.bindgen.sizes().size(ty); } }; @@ -1089,15 +1085,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { // deallocate it. if let AbiVariant::GuestExport = self.variant { if sig.indirect_params && !self.async_ { - let info = self + let ElementInfo { size, align } = self .bindgen .sizes() .record(func.params.iter().map(|t| &t.1)); self.emit(&Instruction::GetArg { nth: 0 }); - self.emit(&Instruction::GuestDeallocate { - size: info.size.size_wasm32(), - align: info.align.align_wasm32(), - }); + self.emit(&Instruction::GuestDeallocate { size, align }); } } @@ -1120,7 +1113,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { nth: sig.params.len() - 1, }); let ptr = self.stack.pop().unwrap(); - self.write_params_to_memory(&func.result, ptr, 0); + self.write_params_to_memory(&func.result, ptr, Default::default()); } // For a guest import this is a function defined in @@ -1129,11 +1122,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { // (statically) and then write the result into that // memory, returning the pointer at the end. AbiVariant::GuestExport => { - let info = self.bindgen.sizes().params(&func.result); - let ptr = self - .bindgen - .return_pointer(info.size.size_wasm32(), info.align.align_wasm32()); - self.write_params_to_memory(&func.result, ptr.clone(), 0); + let ElementInfo { size, align } = + self.bindgen.sizes().params(&func.result); + let ptr = self.bindgen.return_pointer(size, align); + self.write_params_to_memory( + &func.result, + ptr.clone(), + Default::default(), + ); self.stack.push(ptr); } @@ -1185,8 +1181,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&Instruction::GetArg { nth: 0 }); let addr = self.stack.pop().unwrap(); for (offset, ty) in self.bindgen.sizes().field_offsets(&func.result) { - let offset = offset.size_wasm32(); - let offset = i32::try_from(offset).unwrap(); self.deallocate(ty, addr.clone(), offset); } self.emit(&Instruction::Return { func, amt: 0 }); @@ -1273,7 +1267,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&IterElem { element }); self.emit(&IterBasePointer); let addr = self.stack.pop().unwrap(); - self.write_to_memory(element, addr, 0); + self.write_to_memory(element, addr, Default::default()); self.finish_block(0); self.emit(&ListLower { element, realloc }); } @@ -1469,7 +1463,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.push_block(); self.emit(&IterBasePointer); let addr = self.stack.pop().unwrap(); - self.read_from_memory(element, addr, 0); + self.read_from_memory(element, addr, Default::default()); self.finish_block(1); self.emit(&ListLift { element, ty: id }); } @@ -1611,7 +1605,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - fn write_to_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + fn write_to_memory(&mut self, ty: &Type, addr: B::Operand, offset: ArchitectureSize) { use Instruction::*; match *ty { @@ -1671,7 +1665,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { for i in (0..n).rev() { self.stack.push(addr.clone()); self.emit(&I32Store { - offset: offset + (i as i32) * 4, + offset: offset.add_bytes(i * 4), }); } } @@ -1735,24 +1729,19 @@ impl<'a, B: Bindgen> Generator<'a, B> { &mut self, params: impl IntoIterator, addr: B::Operand, - offset: i32, + offset: ArchitectureSize, ) { self.write_fields_to_memory(params, addr, offset); } fn write_variant_arms_to_memory<'b>( &mut self, - offset: i32, + offset: ArchitectureSize, addr: B::Operand, tag: Int, cases: impl IntoIterator> + Clone, ) { - let payload_offset = offset - + (self - .bindgen - .sizes() - .payload_offset(tag, cases.clone()) - .size_wasm32() as i32); + let payload_offset = offset + (self.bindgen.sizes().payload_offset(tag, cases.clone())); for (i, ty) in cases.into_iter().enumerate() { self.push_block(); self.emit(&Instruction::VariantPayloadName); @@ -1768,13 +1757,15 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - fn write_list_to_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + fn write_list_to_memory(&mut self, ty: &Type, addr: B::Operand, offset: ArchitectureSize) { // After lowering the list there's two i32 values on the stack // which we write into memory, writing the pointer into the low address // and the length into the high address. self.lower(ty); self.stack.push(addr.clone()); - self.emit(&Instruction::LengthStore { offset: offset + 4 }); + self.emit(&Instruction::LengthStore { + offset: offset + self.bindgen.sizes().align(ty).into(), + }); self.stack.push(addr); self.emit(&Instruction::PointerStore { offset }); } @@ -1783,7 +1774,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { &mut self, tys: impl IntoIterator, addr: B::Operand, - offset: i32, + offset: ArchitectureSize, ) { let tys = tys.into_iter(); let fields = self @@ -1798,8 +1789,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { .zip(fields) { self.stack.push(op); - let field_offset = field_offset.size_wasm32(); - self.write_to_memory(ty, addr.clone(), offset + (field_offset as i32)); + self.write_to_memory(ty, addr.clone(), offset + (field_offset)); } } @@ -1809,7 +1799,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(instr); } - fn read_from_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + fn read_from_memory(&mut self, ty: &Type, addr: B::Operand, offset: ArchitectureSize) { use Instruction::*; match *ty { @@ -1869,7 +1859,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { for i in 0..n { self.stack.push(addr.clone()); self.emit(&I32Load { - offset: offset + (i as i32) * 4, + offset: offset.add_bytes(i * 4), }); } } @@ -1921,25 +1911,25 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - fn read_results_from_memory(&mut self, result: &Option, addr: B::Operand, offset: i32) { + fn read_results_from_memory( + &mut self, + result: &Option, + addr: B::Operand, + offset: ArchitectureSize, + ) { self.read_fields_from_memory(result, addr, offset) } fn read_variant_arms_from_memory<'b>( &mut self, - offset: i32, + offset: ArchitectureSize, addr: B::Operand, tag: Int, cases: impl IntoIterator> + Clone, ) { self.stack.push(addr.clone()); self.load_intrepr(offset, tag); - let payload_offset = offset - + (self - .bindgen - .sizes() - .payload_offset(tag, cases.clone()) - .size_wasm32() as i32); + let payload_offset = offset + (self.bindgen.sizes().payload_offset(tag, cases.clone())); for ty in cases { self.push_block(); if let Some(ty) = ty { @@ -1949,13 +1939,15 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - fn read_list_from_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + fn read_list_from_memory(&mut self, ty: &Type, addr: B::Operand, offset: ArchitectureSize) { // Read the pointer/len and then perform the standard lifting // proceses. self.stack.push(addr.clone()); self.emit(&Instruction::PointerLoad { offset }); self.stack.push(addr); - self.emit(&Instruction::LengthLoad { offset: offset + 4 }); + self.emit(&Instruction::LengthLoad { + offset: offset + self.bindgen.sizes().align(ty).into(), + }); self.lift(ty); } @@ -1963,11 +1955,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { &mut self, tys: impl IntoIterator, addr: B::Operand, - offset: i32, + offset: ArchitectureSize, ) { for (field_offset, ty) in self.bindgen.sizes().field_offsets(tys).iter() { - let field_offset = field_offset.size_wasm32(); - self.read_from_memory(ty, addr.clone(), offset + (field_offset as i32)); + self.read_from_memory(ty, addr.clone(), offset + (*field_offset)); } } @@ -1977,7 +1968,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.lift(ty); } - fn load_intrepr(&mut self, offset: i32, repr: Int) { + fn load_intrepr(&mut self, offset: ArchitectureSize, repr: Int) { self.emit(&match repr { Int::U64 => Instruction::I64Load { offset }, Int::U32 => Instruction::I32Load { offset }, @@ -1986,7 +1977,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { }); } - fn store_intrepr(&mut self, offset: i32, repr: Int) { + fn store_intrepr(&mut self, offset: ArchitectureSize, repr: Int) { self.emit(&match repr { Int::U64 => Instruction::I64Store { offset }, Int::U32 => Instruction::I32Store { offset }, @@ -1995,7 +1986,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { }); } - fn deallocate(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + fn deallocate(&mut self, ty: &Type, addr: B::Operand, offset: ArchitectureSize) { use Instruction::*; // No need to execute any instructions if this type itself doesn't @@ -2009,7 +2000,9 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.stack.push(addr.clone()); self.emit(&Instruction::PointerLoad { offset }); self.stack.push(addr); - self.emit(&Instruction::LengthLoad { offset: offset + 4 }); + self.emit(&Instruction::LengthLoad { + offset: offset + self.bindgen.sizes().align(ty).into(), + }); self.emit(&Instruction::GuestDeallocateString); } @@ -2034,12 +2027,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.stack.push(addr.clone()); self.emit(&Instruction::PointerLoad { offset }); self.stack.push(addr); - self.emit(&Instruction::LengthLoad { offset: offset + 4 }); + self.emit(&Instruction::LengthLoad { + offset: offset + self.bindgen.sizes().align(ty).into(), + }); self.push_block(); self.emit(&IterBasePointer); let elemaddr = self.stack.pop().unwrap(); - self.deallocate(element, elemaddr, 0); + self.deallocate(element, elemaddr, Default::default()); self.finish_block(0); self.emit(&Instruction::GuestDeallocateList { element }); @@ -2100,19 +2095,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { fn deallocate_variant<'b>( &mut self, - offset: i32, + offset: ArchitectureSize, addr: B::Operand, tag: Int, cases: impl IntoIterator> + Clone, ) { self.stack.push(addr.clone()); self.load_intrepr(offset, tag); - let payload_offset = offset - + (self - .bindgen - .sizes() - .payload_offset(tag, cases.clone()) - .size_wasm32() as i32); + let payload_offset = offset + (self.bindgen.sizes().payload_offset(tag, cases.clone())); for ty in cases { self.push_block(); if let Some(ty) = ty { @@ -2122,10 +2112,9 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - fn deallocate_fields(&mut self, tys: &[Type], addr: B::Operand, offset: i32) { + fn deallocate_fields(&mut self, tys: &[Type], addr: B::Operand, offset: ArchitectureSize) { for (field_offset, ty) in self.bindgen.sizes().field_offsets(tys) { - let field_offset = field_offset.size_wasm32(); - self.deallocate(ty, addr.clone(), offset + (field_offset as i32)); + self.deallocate(ty, addr.clone(), offset + (field_offset)); } } } @@ -2185,7 +2174,3 @@ fn cast(from: WasmType, to: WasmType) -> Bitcast { } } } - -fn align_to(val: usize, align: usize) -> usize { - (val + align - 1) & !(align - 1) -} diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 383ba196c..698d19ba9 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -8,7 +8,10 @@ use std::ops::Deref; use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction}; use wit_bindgen_core::{uwrite, uwriteln, Direction, Ns}; use wit_parser::abi::WasmType; -use wit_parser::{Docs, FunctionKind, Handle, Resolve, SizeAlign, Type, TypeDefKind, TypeId}; +use wit_parser::{ + Alignment, ArchitectureSize, Docs, FunctionKind, Handle, Resolve, SizeAlign, Type, TypeDefKind, + TypeId, +}; /// FunctionBindgen generates the C# code for calling functions defined in wit pub(crate) struct FunctionBindgen<'a, 'b> { @@ -397,22 +400,22 @@ impl Bindgen for FunctionBindgen<'_, '_> { })), Instruction::I32Load { offset } | Instruction::PointerLoad { offset } - | Instruction::LengthLoad { offset } => results.push(format!("BitConverter.ToInt32(new Span((void*)({} + {offset}), 4))",operands[0])), - Instruction::I32Load8U { offset } => results.push(format!("new Span((void*)({} + {offset}), 1)[0]",operands[0])), - Instruction::I32Load8S { offset } => results.push(format!("(sbyte)new Span((void*)({} + {offset}), 1)[0]",operands[0])), - Instruction::I32Load16U { offset } => results.push(format!("BitConverter.ToUInt16(new Span((void*)({} + {offset}), 2))",operands[0])), - Instruction::I32Load16S { offset } => results.push(format!("BitConverter.ToInt16(new Span((void*)({} + {offset}), 2))",operands[0])), - Instruction::I64Load { offset } => results.push(format!("BitConverter.ToInt64(new Span((void*)({} + {offset}), 8))",operands[0])), - Instruction::F32Load { offset } => results.push(format!("BitConverter.ToSingle(new Span((void*)({} + {offset}), 4))",operands[0])), - Instruction::F64Load { offset } => results.push(format!("BitConverter.ToDouble(new Span((void*)({} + {offset}), 8))",operands[0])), + | Instruction::LengthLoad { offset } => results.push(format!("BitConverter.ToInt32(new Span((void*)({} + {offset}), 4))",operands[0],offset = offset.size_wasm32())), + Instruction::I32Load8U { offset } => results.push(format!("new Span((void*)({} + {offset}), 1)[0]",operands[0],offset = offset.size_wasm32())), + Instruction::I32Load8S { offset } => results.push(format!("(sbyte)new Span((void*)({} + {offset}), 1)[0]",operands[0],offset = offset.size_wasm32())), + Instruction::I32Load16U { offset } => results.push(format!("BitConverter.ToUInt16(new Span((void*)({} + {offset}), 2))",operands[0],offset = offset.size_wasm32())), + Instruction::I32Load16S { offset } => results.push(format!("BitConverter.ToInt16(new Span((void*)({} + {offset}), 2))",operands[0],offset = offset.size_wasm32())), + Instruction::I64Load { offset } => results.push(format!("BitConverter.ToInt64(new Span((void*)({} + {offset}), 8))",operands[0],offset = offset.size_wasm32())), + Instruction::F32Load { offset } => results.push(format!("BitConverter.ToSingle(new Span((void*)({} + {offset}), 4))",operands[0],offset = offset.size_wasm32())), + Instruction::F64Load { offset } => results.push(format!("BitConverter.ToDouble(new Span((void*)({} + {offset}), 8))",operands[0],offset = offset.size_wasm32())), Instruction::I32Store { offset } | Instruction::PointerStore { offset } - | Instruction::LengthStore { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 4), {});", operands[1], operands[0]), - Instruction::I32Store8 { offset } => uwriteln!(self.src, "*(byte*)({} + {offset}) = (byte){};", operands[1], operands[0]), - Instruction::I32Store16 { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 2), (short){});", operands[1], operands[0]), - Instruction::I64Store { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 8), unchecked((long){}));", operands[1], operands[0]), - Instruction::F32Store { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 4), unchecked((float){}));", operands[1], operands[0]), - Instruction::F64Store { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 8), unchecked((double){}));", operands[1], operands[0]), + | Instruction::LengthStore { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 4), {});", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::I32Store8 { offset } => uwriteln!(self.src, "*(byte*)({} + {offset}) = (byte){};", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::I32Store16 { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 2), (short){});", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::I64Store { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 8), unchecked((long){}));", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::F32Store { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 4), unchecked((float){}));", operands[1], operands[0],offset = offset.size_wasm32()), + Instruction::F64Store { offset } => uwriteln!(self.src, "BitConverter.TryWriteBytes(new Span((void*)({} + {offset}), 8), unchecked((double){}));", operands[1], operands[0],offset = offset.size_wasm32()), Instruction::I64FromU64 => results.push(format!("unchecked((long)({}))", operands[0])), Instruction::I32FromChar => results.push(format!("((int){})", operands[0])), @@ -1265,15 +1268,16 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - fn return_pointer(&mut self, size: usize, align: usize) -> String { + fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { let ptr = self.locals.tmp("ptr"); match self.interface_gen.direction { Direction::Import => { self.import_return_pointer_area_size = - self.import_return_pointer_area_size.max(size); - self.import_return_pointer_area_align = - self.import_return_pointer_area_align.max(align); + self.import_return_pointer_area_size.max(size.size_wasm32()); + self.import_return_pointer_area_align = self + .import_return_pointer_area_align + .max(align.align_wasm32()); let (array_size, element_type) = crate::world_generator::dotnet_aligned_array( self.import_return_pointer_area_size, self.import_return_pointer_area_align, @@ -1289,16 +1293,23 @@ impl Bindgen for FunctionBindgen<'_, '_> { " var {ret_area} = stackalloc {element_type}[{array_size}+1]; var {ptr} = ((int){ret_area}) + ({align} - 1) & -{align}; - " + ", + align = align.align_wasm32() ); format!("{ptr}") } Direction::Export => { // exports need their return area to be live until the post-return call. - self.interface_gen.csharp_gen.return_area_size = - self.interface_gen.csharp_gen.return_area_size.max(size); - self.interface_gen.csharp_gen.return_area_align = - self.interface_gen.csharp_gen.return_area_align.max(align); + self.interface_gen.csharp_gen.return_area_size = self + .interface_gen + .csharp_gen + .return_area_size + .max(size.size_wasm32()); + self.interface_gen.csharp_gen.return_area_align = self + .interface_gen + .csharp_gen + .return_area_align + .max(align.align_wasm32()); uwrite!( self.src, diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 449eb826d..970ffd4e7 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -6,9 +6,9 @@ use wit_bindgen_core::{ abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType}, dealias, uwrite, uwriteln, wit_parser::{ - Docs, Enum, Flags, FlagsRepr, Function, FunctionKind, Handle, Int, InterfaceId, Record, - Resolve, Result_, SizeAlign, Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, Variant, - WorldId, WorldKey, + Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, FunctionKind, Handle, + Int, InterfaceId, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeDef, TypeDefKind, + TypeId, TypeOwner, Variant, WorldId, WorldKey, }, Direction, Files, InterfaceGenerator as _, Ns, Source, WorldGenerator, }; @@ -280,8 +280,8 @@ pub struct MoonBit { export: HashMap, export_ns: Ns, // return area allocation - return_area_size: usize, - return_area_align: usize, + return_area_size: ArchitectureSize, + return_area_align: Alignment, } impl MoonBit { @@ -615,13 +615,13 @@ impl WorldGenerator for MoonBit { }} " ); - if self.return_area_size != 0 { + if !self.return_area_size.is_empty() { uwriteln!( &mut body, " let return_area : Int = {ffi_qualifier}malloc({}) ", - self.return_area_size, + self.return_area_size.size_wasm32(), ); } files.push( @@ -2470,49 +2470,57 @@ impl Bindgen for FunctionBindgen<'_, '_> { | Instruction::LengthLoad { offset } => results.push(format!( "{}load32(({}) + {offset})", self.gen.qualify_package(FFI_DIR), - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Load8U { offset } => results.push(format!( "{}load8_u(({}) + {offset})", self.gen.qualify_package(FFI_DIR), - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Load8S { offset } => results.push(format!( "{}load8(({}) + {offset})", self.gen.qualify_package(FFI_DIR), - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Load16U { offset } => results.push(format!( "{}load16_u(({}) + {offset})", self.gen.qualify_package(FFI_DIR), - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Load16S { offset } => results.push(format!( "{}load16(({}) + {offset})", self.gen.qualify_package(FFI_DIR), - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I64Load { offset } => results.push(format!( "{}load64(({}) + {offset})", self.gen.qualify_package(FFI_DIR), - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::F32Load { offset } => results.push(format!( "{}loadf32(({}) + {offset})", self.gen.qualify_package(FFI_DIR), - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::F64Load { offset } => results.push(format!( "{}loadf64(({}) + {offset})", self.gen.qualify_package(FFI_DIR), - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Store { offset } @@ -2522,7 +2530,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { "{}store32(({}) + {offset}, {})", self.gen.qualify_package(FFI_DIR), operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::I32Store8 { offset } => uwriteln!( @@ -2530,7 +2539,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { "{}store8(({}) + {offset}, {})", self.gen.qualify_package(FFI_DIR), operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::I32Store16 { offset } => uwriteln!( @@ -2538,7 +2548,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { "{}store16(({}) + {offset}, {})", self.gen.qualify_package(FFI_DIR), operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::I64Store { offset } => uwriteln!( @@ -2546,7 +2557,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { "{}store64(({}) + {offset}, {})", self.gen.qualify_package(FFI_DIR), operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::F32Store { offset } => uwriteln!( @@ -2554,7 +2566,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { "{}storef32(({}) + {offset}, {})", self.gen.qualify_package(FFI_DIR), operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::F64Store { offset } => uwriteln!( @@ -2562,7 +2575,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { "{}storef64(({}) + {offset}, {})", self.gen.qualify_package(FFI_DIR), operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), // TODO: see what we can do with align Instruction::Malloc { size, .. } => { @@ -2570,7 +2584,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.src, "{}malloc({})", self.gen.qualify_package(FFI_DIR), - size + size.size_wasm32() ) } @@ -2677,15 +2691,19 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - fn return_pointer(&mut self, size: usize, align: usize) -> String { + fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { if self.gen.direction == Direction::Import { let ffi_qualifier = self.gen.qualify_package(FFI_DIR); let address = self.locals.tmp("return_area"); - uwriteln!(self.src, "let {address} = {ffi_qualifier}malloc({})", size,); + uwriteln!( + self.src, + "let {address} = {ffi_qualifier}malloc({})", + size.size_wasm32(), + ); self.cleanup.push(Cleanup::Memory { address: address.clone(), - size: size.to_string(), - align, + size: size.size_wasm32().to_string(), + align: align.align_wasm32(), }); address } else { diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index b3d15de3e..7ca7094a9 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -16,14 +16,16 @@ pub(super) struct FunctionBindgen<'a, 'b> { tmp: usize, pub needs_cleanup_list: bool, cleanup: Vec<(String, String)>, - pub import_return_pointer_area_size: usize, - pub import_return_pointer_area_align: usize, + pub import_return_pointer_area_size: ArchitectureSize, + pub import_return_pointer_area_align: Alignment, pub handle_decls: Vec, always_owned: bool, pub async_result_name: Option, emitted_cleanup: bool, } +pub const POINTER_SIZE_EXPRESSION: &str = "::core::mem::size_of::<*const u8>()"; + impl<'a, 'b> FunctionBindgen<'a, 'b> { pub(super) fn new( gen: &'b mut InterfaceGenerator<'a>, @@ -43,8 +45,8 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { tmp: 0, needs_cleanup_list: false, cleanup: Vec::new(), - import_return_pointer_area_size: 0, - import_return_pointer_area_align: 0, + import_return_pointer_area_size: Default::default(), + import_return_pointer_area_align: Default::default(), handle_decls: Vec::new(), always_owned, async_result_name: None, @@ -271,14 +273,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - fn return_pointer(&mut self, size: usize, align: usize) -> String { + fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { let tmp = self.tmp(); // Imports get a per-function return area to facilitate using the // stack whereas exports use a per-module return area to cut down on // stack usage. Note that for imports this also facilitates "adapter // modules" for components to not have data segments. - if size == 0 { + if size.is_empty() { // If the size requested is 0 then we know it won't be written to so // hand out a null pointer. This can happen with async for example // when the params or results are zero-sized. @@ -803,10 +805,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { operand0 = operands[0] )); self.push_str(&format!("let {len} = {vec}.len();\n")); - let size = self.gen.sizes.size(element).size_wasm32(); - let align = self.gen.sizes.align(element).align_wasm32(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); self.push_str(&format!( - "let {layout} = {alloc}::Layout::from_size_align_unchecked({vec}.len() * {size}, {align});\n", + "let {layout} = {alloc}::Layout::from_size_align_unchecked({vec}.len() * {}, {});\n", + size.format(POINTER_SIZE_EXPRESSION), align.format(POINTER_SIZE_EXPRESSION), )); self.push_str(&format!("let {result} = if {layout}.size() != 0 {{\n")); self.push_str(&format!( @@ -817,7 +820,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { )); self.push_str("else {\n::core::ptr::null_mut()\n};\n"); self.push_str(&format!("for (i, e) in {vec}.into_iter().enumerate() {{\n",)); - self.push_str(&format!("let base = {result}.add(i * {size});\n",)); + self.push_str(&format!( + "let base = {result}.add(i * {});\n", + size.format(POINTER_SIZE_EXPRESSION) + )); self.push_str(&body); self.push_str("\n}\n"); results.push(format!("{result}")); @@ -834,8 +840,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::ListLift { element, .. } => { let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); - let size = self.gen.sizes.size(element).size_wasm32(); - let align = self.gen.sizes.align(element).align_wasm32(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); let len = format!("len{tmp}"); let base = format!("base{tmp}"); let result = format!("result{tmp}"); @@ -853,13 +859,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { )); uwriteln!(self.src, "for i in 0..{len} {{"); - uwriteln!(self.src, "let base = {base}.add(i * {size});"); + uwriteln!( + self.src, + "let base = {base}.add(i * {size});", + size = size.format(POINTER_SIZE_EXPRESSION) + ); uwriteln!(self.src, "let e{tmp} = {body};"); uwriteln!(self.src, "{result}.push(e{tmp});"); uwriteln!(self.src, "}}"); results.push(result); let dealloc = self.gen.path_to_cabi_dealloc(); - self.push_str(&format!("{dealloc}({base}, {len} * {size}, {align});\n",)); + self.push_str(&format!( + "{dealloc}({base}, {len} * {size}, {align});\n", + size = size.format(POINTER_SIZE_EXPRESSION), + align = align.format(POINTER_SIZE_EXPRESSION) + )); } Instruction::IterElem { .. } => results.push("e".to_string()), @@ -1042,7 +1056,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = *{}.add({offset}).cast::();", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1051,7 +1066,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = i32::from(*{0}.add({offset}).cast::());", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1060,7 +1076,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = i32::from(*{}.add({offset}).cast::());", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1069,7 +1086,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = i32::from(*{}.add({offset}).cast::());", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1078,7 +1096,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = i32::from(*{}.add({offset}).cast::());", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1087,7 +1106,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = *{}.add({offset}).cast::();", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1096,7 +1116,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = *{}.add({offset}).cast::();", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1105,7 +1126,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = *{}.add({offset}).cast::();", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1115,7 +1137,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "let l{tmp} = *{}.add({offset}).cast::<*mut u8>();", - operands[0] + operands[0], + offset = offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1123,8 +1146,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { let tmp = self.tmp(); uwriteln!( self.src, - "let l{tmp} = *{}.add({offset}).cast::();", - operands[0] + "let l{tmp} = *{}.add({}).cast::();", + operands[0], + offset.format_term(POINTER_SIZE_EXPRESSION, true) ); results.push(format!("l{tmp}")); } @@ -1132,50 +1156,66 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Store { offset } => { self.push_str(&format!( "*{}.add({}).cast::() = {};\n", - operands[1], offset, operands[0] + operands[1], + offset.format_term(POINTER_SIZE_EXPRESSION, true), + operands[0] )); } Instruction::I32Store8 { offset } => { self.push_str(&format!( "*{}.add({}).cast::() = ({}) as u8;\n", - operands[1], offset, operands[0] + operands[1], + offset.format_term(POINTER_SIZE_EXPRESSION, true), + operands[0] )); } Instruction::I32Store16 { offset } => { self.push_str(&format!( "*{}.add({}).cast::() = ({}) as u16;\n", - operands[1], offset, operands[0] + operands[1], + offset.format_term(POINTER_SIZE_EXPRESSION, true), + operands[0] )); } Instruction::I64Store { offset } => { self.push_str(&format!( "*{}.add({}).cast::() = {};\n", - operands[1], offset, operands[0] + operands[1], + offset.format_term(POINTER_SIZE_EXPRESSION, true), + operands[0] )); } Instruction::F32Store { offset } => { self.push_str(&format!( "*{}.add({}).cast::() = {};\n", - operands[1], offset, operands[0] + operands[1], + offset.format_term(POINTER_SIZE_EXPRESSION, true), + operands[0] )); } Instruction::F64Store { offset } => { self.push_str(&format!( "*{}.add({}).cast::() = {};\n", - operands[1], offset, operands[0] + operands[1], + offset.format_term(POINTER_SIZE_EXPRESSION, true), + operands[0] )); } Instruction::PointerStore { offset } => { self.push_str(&format!( "*{}.add({}).cast::<*mut u8>() = {};\n", - operands[1], offset, operands[0] + operands[1], + offset.format_term(POINTER_SIZE_EXPRESSION, true), + operands[0] )); } Instruction::LengthStore { offset } => { self.push_str(&format!( "*{}.add({}).cast::() = {};\n", - operands[1], offset, operands[0] + operands[1], + offset.format_term(POINTER_SIZE_EXPRESSION, true), + operands[0] )); } @@ -1185,7 +1225,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { let dealloc = self.gen.path_to_cabi_dealloc(); self.push_str(&format!( "{dealloc}({op}, {size}, {align});\n", - op = operands[0] + op = operands[0], + size = size.format_term(POINTER_SIZE_EXPRESSION, true), + align = align.format(POINTER_SIZE_EXPRESSION) )); } @@ -1220,8 +1262,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::GuestDeallocateList { element } => { let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); - let size = self.gen.sizes.size(element).size_wasm32(); - let align = self.gen.sizes.align(element).align_wasm32(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); let len = format!("len{tmp}"); let base = format!("base{tmp}"); self.push_str(&format!( @@ -1240,13 +1282,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str("let base = "); self.push_str(&base); self.push_str(".add(i * "); - self.push_str(&size.to_string()); + self.push_str(&size.format(POINTER_SIZE_EXPRESSION)); self.push_str(");\n"); self.push_str(&body); self.push_str("\n}\n"); } let dealloc = self.gen.path_to_cabi_dealloc(); - self.push_str(&format!("{dealloc}({base}, {len} * {size}, {align});\n",)); + self.push_str(&format!( + "{dealloc}({base}, {len} * {size}, {align});\n", + size = size.format(POINTER_SIZE_EXPRESSION), + align = align.format(POINTER_SIZE_EXPRESSION) + )); } } } diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index f99fe1eeb..4e22346fc 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1,4 +1,4 @@ -use crate::bindgen::FunctionBindgen; +use crate::bindgen::{FunctionBindgen, POINTER_SIZE_EXPRESSION}; use crate::{ full_wit_type_name, int_repr, to_rust_ident, to_upper_camel_case, wasm_type, AsyncConfig, FnSig, Identifier, InterfaceName, Ownership, RuntimeItem, RustFlagsRepr, RustWasm, @@ -22,8 +22,8 @@ pub struct InterfaceGenerator<'a> { pub(super) gen: &'a mut RustWasm, pub wasm_import_module: &'a str, pub resolve: &'a Resolve, - pub return_pointer_area_size: usize, - pub return_pointer_area_align: usize, + pub return_pointer_area_size: ArchitectureSize, + pub return_pointer_area_align: Alignment, pub(super) needs_runtime_module: bool, } @@ -377,17 +377,29 @@ macro_rules! {macro_name} {{ } } + pub fn align_area(&mut self, alignment: Alignment) { + match alignment { + Alignment::Pointer => uwriteln!( + self.src, + "#[cfg_attr(target_pointer_width=\"64\", repr(align(8)))] + #[cfg_attr(target_pointer_width=\"32\", repr(align(4)))]" + ), + Alignment::Bytes(bytes) => { + uwriteln!(self.src, "#[repr(align({align}))]", align = bytes.get()) + } + } + } + pub fn finish(&mut self) -> String { - if self.return_pointer_area_align > 0 { + if !self.return_pointer_area_size.is_empty() { + uwriteln!(self.src,); + self.align_area(self.return_pointer_area_align); uwrite!( self.src, - "\ - #[repr(align({align}))] - struct _RetArea([::core::mem::MaybeUninit::; {size}]); + "struct _RetArea([::core::mem::MaybeUninit::; {size}]); static mut _RET_AREA: _RetArea = _RetArea([::core::mem::MaybeUninit::uninit(); {size}]); ", - align = self.return_pointer_area_align, - size = self.return_pointer_area_size, + size = self.return_pointer_area_size.format_term(POINTER_SIZE_EXPRESSION, true), ); } @@ -898,14 +910,14 @@ pub mod vtable{ordinal} {{ uwriteln!(self.src, "let mut cleanup_list = {vec}::new();"); } assert!(handle_decls.is_empty()); - if import_return_pointer_area_size > 0 { + if !import_return_pointer_area_size.is_empty() { + uwriteln!(self.src,); + self.align_area(import_return_pointer_area_align); uwrite!( self.src, - "\ - #[repr(align({import_return_pointer_area_align}))] - struct RetArea([::core::mem::MaybeUninit::; {import_return_pointer_area_size}]); + "struct RetArea([::core::mem::MaybeUninit::; {import_return_pointer_area_size}]); let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); {import_return_pointer_area_size}]); -", +", import_return_pointer_area_size = import_return_pointer_area_size.format_term(POINTER_SIZE_EXPRESSION, true) ); } self.src.push_str(&String::from(src)); diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 5fabffdc7..f72f76f83 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -350,8 +350,8 @@ impl RustWasm { gen: self, sizes, resolve, - return_pointer_area_size: 0, - return_pointer_area_align: 0, + return_pointer_area_size: Default::default(), + return_pointer_area_align: Default::default(), needs_runtime_module: false, } } diff --git a/crates/teavm-java/src/lib.rs b/crates/teavm-java/src/lib.rs index 681537178..25e3732a5 100644 --- a/crates/teavm-java/src/lib.rs +++ b/crates/teavm-java/src/lib.rs @@ -10,9 +10,9 @@ use wit_bindgen_core::{ abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType}, uwrite, uwriteln, wit_parser::{ - Docs, Enum, Flags, FlagsRepr, Function, FunctionKind, Int, InterfaceId, Record, Resolve, - Result_, SizeAlign, Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, Variant, WorldId, - WorldKey, + Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, FunctionKind, Int, + InterfaceId, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeDef, TypeDefKind, + TypeId, TypeOwner, Variant, WorldId, WorldKey, }, Direction, Files, InterfaceGenerator as _, Ns, Source, WorldGenerator, }; @@ -53,8 +53,8 @@ struct InterfaceFragment { pub struct TeaVmJava { opts: Opts, name: String, - return_area_size: usize, - return_area_align: usize, + return_area_size: ArchitectureSize, + return_area_align: Alignment, tuple_counts: HashSet, needs_cleanup: bool, needs_result: bool, @@ -345,9 +345,9 @@ impl WorldGenerator for TeaVmJava { ); } - if self.return_area_align > 0 { - let size = self.return_area_size; - let align = self.return_area_align; + if !self.return_area_size.is_empty() { + let size = self.return_area_size.size_wasm32(); + let align = self.return_area_align.align_wasm32(); uwriteln!( src, @@ -1666,8 +1666,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { assert!(block_results.is_empty()); let op = &operands[0]; - let size = self.gen.gen.sizes.size(element).size_wasm32(); - let align = self.gen.gen.sizes.align(element).align_wasm32(); + let size = self.gen.gen.sizes.size(element); + let align = self.gen.gen.sizes.align(element); let address = self.locals.tmp("address"); let ty = self.gen.type_name(element); let index = self.locals.tmp("index"); @@ -1681,14 +1681,16 @@ impl Bindgen for FunctionBindgen<'_, '_> { int {base} = {address} + ({index} * {size}); {body} }} - " + ", + align = align.align_wasm32(), + size = size.size_wasm32() ); if realloc.is_none() { self.cleanup.push(Cleanup { address: address.clone(), - size: format!("({op}).size() * {size}"), - align, + size: format!("({op}).size() * {size}", size = size.size_wasm32()), + align: align.align_wasm32(), }); } @@ -1831,42 +1833,50 @@ impl Bindgen for FunctionBindgen<'_, '_> { | Instruction::PointerLoad { offset } | Instruction::LengthLoad { offset } => results.push(format!( "org.teavm.interop.Address.fromInt(({}) + {offset}).getInt()", - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Load8U { offset } => results.push(format!( "(((int) org.teavm.interop.Address.fromInt(({}) + {offset}).getByte()) & 0xFF)", - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Load8S { offset } => results.push(format!( "((int) org.teavm.interop.Address.fromInt(({}) + {offset}).getByte())", - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Load16U { offset } => results.push(format!( "(((int) org.teavm.interop.Address.fromInt(({}) + {offset}).getShort()) & 0xFFFF)", - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Load16S { offset } => results.push(format!( "((int) org.teavm.interop.Address.fromInt(({}) + {offset}).getShort())", - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I64Load { offset } => results.push(format!( "org.teavm.interop.Address.fromInt(({}) + {offset}).getLong()", - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::F32Load { offset } => results.push(format!( "org.teavm.interop.Address.fromInt(({}) + {offset}).getFloat()", - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::F64Load { offset } => results.push(format!( "org.teavm.interop.Address.fromInt(({}) + {offset}).getDouble()", - operands[0] + operands[0], + offset = offset.size_wasm32() )), Instruction::I32Store { offset } @@ -1875,42 +1885,48 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.src, "org.teavm.interop.Address.fromInt(({}) + {offset}).putInt({});", operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::I32Store8 { offset } => uwriteln!( self.src, "org.teavm.interop.Address.fromInt(({}) + {offset}).putByte((byte) ({}));", operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::I32Store16 { offset } => uwriteln!( self.src, "org.teavm.interop.Address.fromInt(({}) + {offset}).putShort((short) ({}));", operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::I64Store { offset } => uwriteln!( self.src, "org.teavm.interop.Address.fromInt(({}) + {offset}).putLong({});", operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::F32Store { offset } => uwriteln!( self.src, "org.teavm.interop.Address.fromInt(({}) + {offset}).putFloat({});", operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::F64Store { offset } => uwriteln!( self.src, "org.teavm.interop.Address.fromInt(({}) + {offset}).putDouble({});", operands[1], - operands[0] + operands[0], + offset = offset.size_wasm32() ), Instruction::Malloc { .. } => unimplemented!(), @@ -1919,7 +1935,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "Memory.free(org.teavm.interop.Address.fromInt({}), {size}, {align});", - operands[0] + operands[0], + size = size.size_wasm32(), + align = align.align_wasm32() ) } @@ -2011,7 +2029,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - fn return_pointer(&mut self, size: usize, align: usize) -> String { + fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { self.gen.gen.return_area_size = self.gen.gen.return_area_size.max(size); self.gen.gen.return_area_align = self.gen.gen.return_area_align.max(align); format!("{}RETURN_AREA", self.gen.gen.qualifier()) From 86e8ae2b8b97f11b73b273345b0e00340f017270 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Tue, 11 Mar 2025 09:55:32 -0700 Subject: [PATCH 06/14] [c#] Enable running the C# tests on linux (#1200) * Enable running the Csharp tests on linux Signed-off-by: James Sturtevant * fix ci Signed-off-by: James Sturtevant * We don't have native aot packages for macos avaliable Signed-off-by: James Sturtevant * Don't silently pass on macOS Instead just don't test on macos * Different github actions syntax --------- Signed-off-by: James Sturtevant Co-authored-by: Alex Crichton --- .github/workflows/main.yml | 6 +++++- crates/csharp/src/csproj.rs | 13 ++++++++++--- tests/runtime/main.rs | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index be739cd4b..4d65d85ee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -60,6 +60,10 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] # moonbit removed from language matrix for now - causing CI failures lang: [c, rust, teavm-java, go, csharp] + exclude: + # For now csharp doesn't work on macos, so exclude it from testing. + - os: macos-latest + lang: csharp runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -73,7 +77,7 @@ jobs: if: matrix.lang == 'rust' - uses: ./.github/actions/install-wasi-sdk - if: matrix.lang == 'c' || (matrix.lang == 'csharp' && matrix.os == 'windows-latest') + if: matrix.lang == 'c' || matrix.lang == 'csharp' - name: Setup .NET uses: actions/setup-dotnet@v4 diff --git a/crates/csharp/src/csproj.rs b/crates/csharp/src/csproj.rs index 415472614..f279757cc 100644 --- a/crates/csharp/src/csproj.rs +++ b/crates/csharp/src/csproj.rs @@ -91,13 +91,20 @@ impl CSProjectLLVMBuilder { ); if self.aot { + let os = match std::env::consts::OS { + "windows" => "win", + "linux" => std::env::consts::OS, + other => todo!("OS {} not supported", other), + }; + csproj.push_str( - r#" + &format!( + r#" - + - "#, + "#), ); fs::write( diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 60c66ce91..7c801b011 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -659,7 +659,7 @@ fn tests(name: &str, dir_name: &str) -> Result> { } #[cfg(feature = "csharp")] - if cfg!(windows) && !c_sharp.is_empty() { + if !c_sharp.is_empty() { let (resolve, world) = resolve_wit_dir(&dir); for path in c_sharp.iter() { let world_name = &resolve.worlds[world].name; From 132b4fe331331c3b90c1ce07f52a7408ca1572e0 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Mar 2025 14:22:31 -0500 Subject: [PATCH 07/14] Remove TeaVM-WASI support (#1202) This has been unmaintained for quite some time now and has never reached feature parity with other backends. In preparation for #1192 this commit removes the teavm-java generator entirely with a "tombstone" left in the README about the last commit which had the code. --- .github/workflows/main.yml | 11 +- Cargo.toml | 4 - README.md | 13 +- ci/download-teavm.sh | 8 - ci/publish.rs | 1 - crates/teavm-java/Cargo.toml | 22 - crates/teavm-java/src/lib.rs | 2289 ---------------------------- crates/teavm-java/tests/Main.java | 3 - crates/teavm-java/tests/codegen.rs | 108 -- src/bin/wit-bindgen.rs | 10 - tests/runtime/main.rs | 151 -- 11 files changed, 8 insertions(+), 2612 deletions(-) delete mode 100755 ci/download-teavm.sh delete mode 100644 crates/teavm-java/Cargo.toml delete mode 100644 crates/teavm-java/src/lib.rs delete mode 100644 crates/teavm-java/tests/Main.java delete mode 100644 crates/teavm-java/tests/codegen.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4d65d85ee..69d759fe3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] # moonbit removed from language matrix for now - causing CI failures - lang: [c, rust, teavm-java, go, csharp] + lang: [c, rust, go, csharp] exclude: # For now csharp doesn't work on macos, so exclude it from testing. - os: macos-latest @@ -99,14 +99,6 @@ jobs: shell: powershell if: matrix.os == 'windows-latest' && matrix.lang == 'moonbit' - - run: ci/download-teavm.sh - if: matrix.lang == 'teavm-java' - - uses: actions/setup-java@v4 - if: matrix.lang == 'teavm-java' - with: - java-version: '18' - distribution: 'adopt' - - uses: actions/setup-go@v4 if: matrix.lang == 'go' with: @@ -160,7 +152,6 @@ jobs: - run: cargo build --no-default-features - run: cargo build --no-default-features --features rust - run: cargo build --no-default-features --features c - - run: cargo build --no-default-features --features teavm-java - run: cargo build --no-default-features --features go - run: cargo build --no-default-features --features csharp - run: cargo build --no-default-features --features markdown diff --git a/Cargo.toml b/Cargo.toml index bcd04e3df..372cc52d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ wit-component = "0.227.0" wit-bindgen-core = { path = 'crates/core', version = '0.40.0' } wit-bindgen-c = { path = 'crates/c', version = '0.40.0' } wit-bindgen-rust = { path = "crates/rust", version = "0.40.0" } -wit-bindgen-teavm-java = { path = 'crates/teavm-java', version = '0.40.0' } wit-bindgen-go = { path = 'crates/go', version = '0.40.0' } wit-bindgen-csharp = { path = 'crates/csharp', version = '0.40.0' } wit-bindgen-markdown = { path = 'crates/markdown', version = '0.40.0' } @@ -60,7 +59,6 @@ wit-bindgen-rust = { workspace = true, features = ['clap'], optional = true } wit-bindgen-c = { workspace = true, features = ['clap'], optional = true } wit-bindgen-markdown = { workspace = true, features = ['clap'], optional = true } wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true } -wit-bindgen-teavm-java = { workspace = true, features = ['clap'], optional = true } wit-bindgen-go = { workspace = true, features = ['clap'], optional = true } wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } wit-component = { workspace = true } @@ -71,7 +69,6 @@ default = [ 'c', 'rust', 'markdown', - 'teavm-java', 'go', 'csharp', 'moonbit', @@ -80,7 +77,6 @@ default = [ c = ['dep:wit-bindgen-c'] rust = ['dep:wit-bindgen-rust'] markdown = ['dep:wit-bindgen-markdown'] -teavm-java = ['dep:wit-bindgen-teavm-java'] go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] diff --git a/README.md b/README.md index e1e125651..5fb9d1f90 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ cd MyApp dotnet new nugetconfig ``` -In the `nuget.config` after ``make sure you have: +In the `nuget.config` after ``make sure you have: ``` @@ -347,7 +347,7 @@ In the MyApp.csproj add the following to the property group: Add the native-aot compiler (substitute `win-x64` for `linux-x64` on Linux): ``` -dotnet add package Microsoft.DotNet.ILCompiler.LLVM --prerelease +dotnet add package Microsoft.DotNet.ILCompiler.LLVM --prerelease dotnet add package runtime.win-x64.Microsoft.DotNet.ILCompiler.LLVM --prerelease ``` @@ -361,10 +361,11 @@ Checkout out [componentize-dotnet](https://github.com/bytecodealliance/component ### Guest: Java -Java bytecode can be compiled to WebAssembly using -[TeaVM-WASI](https://github.com/fermyon/teavm-wasi). With this generator, -`wit-bindgen` will emit `*.java` files which may be used with any JVM language, -e.g. Java, Kotlin, Clojure, Scala, etc. +This project historically had some support for +[TeaVM-WASI](https://github.com/fermyon/teavm-wasi), but it was unmaintained for +a long time and never was at feature parity with other generators, so it was +removed. The last commit with support for TeaVM-WASI was +https://github.com/bytecodealliance/wit-bindgen/commit/86e8ae2b8b97f11b73b273345b0e00340f017270. ### Guest: TinyGo diff --git a/ci/download-teavm.sh b/ci/download-teavm.sh deleted file mode 100755 index 29bc8aa1f..000000000 --- a/ci/download-teavm.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -set -ex - -mkdir -p target -cd target -curl -O https://repo.maven.apache.org/maven2/com/fermyon/teavm-cli/0.2.8/teavm-cli-0.2.8.jar -curl -O https://repo.maven.apache.org/maven2/com/fermyon/teavm-interop/0.2.8/teavm-interop-0.2.8.jar diff --git a/ci/publish.rs b/ci/publish.rs index b4fb38060..ed18210db 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -22,7 +22,6 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wit-bindgen-rust", "wit-bindgen-go", "wit-bindgen-csharp", - "wit-bindgen-teavm-java", "wit-bindgen-markdown", "wit-bindgen-moonbit", "wit-bindgen-rust-macro", diff --git a/crates/teavm-java/Cargo.toml b/crates/teavm-java/Cargo.toml deleted file mode 100644 index f14d5de2d..000000000 --- a/crates/teavm-java/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "wit-bindgen-teavm-java" -version = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -license = { workspace = true } -homepage = 'https://github.com/bytecodealliance/wit-bindgen' -description = """ -TeaVM-Java bindings generator for WIT and the component model, typically used -through the `wit-bindgen-cli` crate. -""" - -[dependencies] -anyhow = { workspace = true } -wit-bindgen-core = { workspace = true } -wit-component = { workspace = true } -wasm-metadata = { workspace = true } -heck = { workspace = true } -clap = { workspace = true, optional = true } - -[dev-dependencies] -test-helpers = { path = '../test-helpers' } diff --git a/crates/teavm-java/src/lib.rs b/crates/teavm-java/src/lib.rs deleted file mode 100644 index 25e3732a5..000000000 --- a/crates/teavm-java/src/lib.rs +++ /dev/null @@ -1,2289 +0,0 @@ -use anyhow::Result; -use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; -use std::{ - collections::{HashMap, HashSet}, - fmt::Write, - iter, mem, - ops::Deref, -}; -use wit_bindgen_core::{ - abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType}, - uwrite, uwriteln, - wit_parser::{ - Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, FunctionKind, Int, - InterfaceId, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeDef, TypeDefKind, - TypeId, TypeOwner, Variant, WorldId, WorldKey, - }, - Direction, Files, InterfaceGenerator as _, Ns, Source, WorldGenerator, -}; - -const IMPORTS: &str = "\ -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; - -import org.teavm.interop.Memory; -import org.teavm.interop.Address; -import org.teavm.interop.Import; -import org.teavm.interop.Export;\ -"; - -#[derive(Default, Debug, Clone)] -#[cfg_attr(feature = "clap", derive(clap::Args))] -pub struct Opts { - /// Whether or not to generate a stub class for exported functions - #[cfg_attr(feature = "clap", arg(long))] - pub generate_stub: bool, -} - -impl Opts { - pub fn build(&self) -> Box { - Box::new(TeaVmJava { - opts: self.clone(), - ..TeaVmJava::default() - }) - } -} - -struct InterfaceFragment { - src: String, - stub: String, -} - -#[derive(Default)] -pub struct TeaVmJava { - opts: Opts, - name: String, - return_area_size: ArchitectureSize, - return_area_align: Alignment, - tuple_counts: HashSet, - needs_cleanup: bool, - needs_result: bool, - interface_fragments: HashMap>, - world_fragments: Vec, - sizes: SizeAlign, - interface_names: HashMap, -} - -impl TeaVmJava { - fn qualifier(&self) -> String { - format!("{}.", self.name) - } - - fn interface<'a>(&'a mut self, resolve: &'a Resolve, name: &'a str) -> InterfaceGenerator<'a> { - InterfaceGenerator { - src: String::new(), - stub: String::new(), - gen: self, - resolve, - name, - } - } -} - -impl WorldGenerator for TeaVmJava { - fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { - self.name = world_name(resolve, world); - self.sizes.fill(resolve); - } - - fn import_interface( - &mut self, - resolve: &Resolve, - key: &WorldKey, - id: InterfaceId, - _files: &mut Files, - ) -> Result<()> { - let name = interface_name(resolve, key, Direction::Import); - self.interface_names.insert(id, name.clone()); - let mut gen = self.interface(resolve, &name); - gen.types(id); - - for (_, func) in resolve.interfaces[id].functions.iter() { - gen.import(&resolve.name_world_key(key), func); - } - - gen.add_interface_fragment(); - - Ok(()) - } - - fn import_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) { - let name = world_name(resolve, world); - let mut gen = self.interface(resolve, &name); - - for (_, func) in funcs { - gen.import("$root", func); - } - - gen.add_world_fragment(); - } - - fn export_interface( - &mut self, - resolve: &Resolve, - key: &WorldKey, - id: InterfaceId, - _files: &mut Files, - ) -> Result<()> { - let name = interface_name(resolve, key, Direction::Export); - self.interface_names.insert(id, name.clone()); - let mut gen = self.interface(resolve, &name); - gen.types(id); - - for (_, func) in resolve.interfaces[id].functions.iter() { - gen.export(Some(&resolve.name_world_key(key)), func); - } - - gen.add_interface_fragment(); - Ok(()) - } - - fn export_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) -> Result<()> { - let name = world_name(resolve, world); - let mut gen = self.interface(resolve, &name); - - for (_, func) in funcs { - gen.export(None, func); - } - - gen.add_world_fragment(); - Ok(()) - } - - fn import_types( - &mut self, - resolve: &Resolve, - world: WorldId, - types: &[(&str, TypeId)], - _files: &mut Files, - ) { - let name = world_name(resolve, world); - let mut gen = self.interface(resolve, &name); - - for (ty_name, ty) in types { - gen.define_type(ty_name, *ty); - } - - gen.add_world_fragment(); - } - - fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { - let name = world_name(resolve, id); - let (package, name) = split_qualified_name(&name); - - let mut src = Source::default(); - let version = env!("CARGO_PKG_VERSION"); - wit_bindgen_core::generated_preamble(&mut src, version); - - uwrite!( - src, - "package {package}; - - {IMPORTS} - import org.teavm.interop.CustomSection; - - public final class {name} {{ - private {name}() {{}} - " - ); - - src.push_str( - &self - .world_fragments - .iter() - .map(|f| f.src.deref()) - .collect::>() - .join("\n"), - ); - - let mut producers = wasm_metadata::Producers::empty(); - producers.add( - "processed-by", - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_VERSION"), - ); - - let component_type = wit_component::metadata::encode( - resolve, - id, - wit_component::StringEncoding::UTF8, - Some(&producers), - ) - .unwrap(); - - let component_type = component_type - .into_iter() - .map(|byte| format!("{byte:02x}")) - .collect::>() - .concat(); - - uwriteln!( - src, - r#" - @CustomSection(name = "component-type:{name}") - private static final String __WIT_BINDGEN_COMPONENT_TYPE = "{component_type}"; - "# - ); - - for &count in &self.tuple_counts { - let (type_params, instance) = if count == 0 { - ( - String::new(), - "public static final Tuple0 INSTANCE = new Tuple0();", - ) - } else { - ( - format!( - "<{}>", - (0..count) - .map(|index| format!("T{index}")) - .collect::>() - .join(", ") - ), - "", - ) - }; - let value_params = (0..count) - .map(|index| format!("T{index} f{index}")) - .collect::>() - .join(", "); - let fields = (0..count) - .map(|index| format!("public final T{index} f{index};")) - .collect::>() - .join("\n"); - let inits = (0..count) - .map(|index| format!("this.f{index} = f{index};")) - .collect::>() - .join("\n"); - - uwrite!( - src, - " - public static final class Tuple{count}{type_params} {{ - {fields} - - public Tuple{count}({value_params}) {{ - {inits} - }} - - {instance} - }} - " - ) - } - - if self.needs_result { - src.push_str( - r#" - public static final class Result { - public final byte tag; - private final Object value; - - private Result(byte tag, Object value) { - this.tag = tag; - this.value = value; - } - - public static Result ok(Ok ok) { - return new Result<>(OK, ok); - } - - public static Result err(Err err) { - return new Result<>(ERR, err); - } - - public Ok getOk() { - if (this.tag == OK) { - return (Ok) this.value; - } else { - throw new RuntimeException("expected OK, got " + this.tag); - } - } - - public Err getErr() { - if (this.tag == ERR) { - return (Err) this.value; - } else { - throw new RuntimeException("expected ERR, got " + this.tag); - } - } - - public static final byte OK = 0; - public static final byte ERR = 1; - } - "#, - ) - } - - if self.needs_cleanup { - src.push_str( - " - public static final class Cleanup { - public final int address; - public final int size; - public final int align; - - public Cleanup(int address, int size, int align) { - this.address = address; - this.size = size; - this.align = align; - } - } - ", - ); - } - - if !self.return_area_size.is_empty() { - let size = self.return_area_size.size_wasm32(); - let align = self.return_area_align.align_wasm32(); - - uwriteln!( - src, - "public static final int RETURN_AREA = Memory.malloc({size}, {align}).toInt();", - ); - } - - src.push_str("}\n"); - - let directory = package.replace('.', "/"); - files.push(&format!("{directory}/{name}.java"), indent(&src).as_bytes()); - - let generate_stub = - |package: &str, name, fragments: &[InterfaceFragment], files: &mut Files| { - let b = fragments - .iter() - .map(|f| f.stub.deref()) - .collect::>() - .join("\n"); - - let mut body = Source::default(); - wit_bindgen_core::generated_preamble(&mut body, version); - uwriteln!( - &mut body, - "package {package}; - - {IMPORTS} - - public class {name} {{ - {b} - }} - " - ); - - let directory = package.replace('.', "/"); - files.push( - &format!("{directory}/{name}.java"), - indent(&body).as_bytes(), - ); - }; - - if self.opts.generate_stub { - generate_stub( - &package, - format!("{name}Impl"), - &self.world_fragments, - files, - ); - } - - for (name, fragments) in &self.interface_fragments { - let (package, name) = split_qualified_name(name); - - let b = fragments - .iter() - .map(|f| f.src.deref()) - .collect::>() - .join("\n"); - - let mut body = Source::default(); - wit_bindgen_core::generated_preamble(&mut body, version); - uwriteln!( - &mut body, - "package {package}; - - {IMPORTS} - - public final class {name} {{ - private {name}() {{}} - - {b} - }} - " - ); - - let directory = package.replace('.', "/"); - files.push( - &format!("{directory}/{name}.java"), - indent(&body).as_bytes(), - ); - - if self.opts.generate_stub { - generate_stub(&package, format!("{name}Impl"), fragments, files); - } - } - - Ok(()) - } -} - -struct InterfaceGenerator<'a> { - src: String, - stub: String, - gen: &'a mut TeaVmJava, - resolve: &'a Resolve, - name: &'a str, -} - -impl InterfaceGenerator<'_> { - fn qualifier(&self, when: bool, ty: &TypeDef) -> String { - if let TypeOwner::Interface(id) = &ty.owner { - if let Some(name) = self.gen.interface_names.get(id) { - if name != self.name { - return format!("{name}."); - } - } - } - - if when { - format!("{}.", self.name) - } else { - String::new() - } - } - - fn add_interface_fragment(self) { - self.gen - .interface_fragments - .entry(self.name.to_owned()) - .or_default() - .push(InterfaceFragment { - src: self.src, - stub: self.stub, - }); - } - - fn add_world_fragment(self) { - self.gen.world_fragments.push(InterfaceFragment { - src: self.src, - stub: self.stub, - }); - } - - fn import(&mut self, module: &str, func: &Function) { - if func.kind != FunctionKind::Freestanding { - todo!("resources"); - } - - let mut bindgen = FunctionBindgen::new( - self, - &func.name, - func.params - .iter() - .map(|(name, _)| name.to_java_ident()) - .collect(), - ); - - abi::call( - bindgen.gen.resolve, - AbiVariant::GuestImport, - LiftLower::LowerArgsLiftResults, - func, - &mut bindgen, - false, - ); - - let src = bindgen.src; - - let cleanup_list = if bindgen.needs_cleanup_list { - self.gen.needs_cleanup = true; - - format!( - "ArrayList<{}Cleanup> cleanupList = new ArrayList<>();\n", - self.gen.qualifier() - ) - } else { - String::new() - }; - - let name = &func.name; - - let sig = self.resolve.wasm_signature(AbiVariant::GuestImport, func); - - let result_type = match &sig.results[..] { - [] => "void", - [result] => wasm_type(*result), - _ => unreachable!(), - }; - - let camel_name = func.name.to_upper_camel_case(); - - let params = sig - .params - .iter() - .enumerate() - .map(|(i, param)| { - let ty = wasm_type(*param); - format!("{ty} p{i}") - }) - .collect::>() - .join(", "); - - let sig = self.sig_string(func, false); - - uwrite!( - self.src, - r#"@Import(name = "{name}", module = "{module}") - private static native {result_type} wasmImport{camel_name}({params}); - - {sig} {{ - {cleanup_list} {src} - }} - "# - ); - } - - fn export(&mut self, interface_name: Option<&str>, func: &Function) { - let sig = self.resolve.wasm_signature(AbiVariant::GuestExport, func); - - let export_name = func.legacy_core_export_name(interface_name); - - let mut bindgen = FunctionBindgen::new( - self, - &func.name, - (0..sig.params.len()).map(|i| format!("p{i}")).collect(), - ); - - abi::call( - bindgen.gen.resolve, - AbiVariant::GuestExport, - LiftLower::LiftArgsLowerResults, - func, - &mut bindgen, - false, - ); - - assert!(!bindgen.needs_cleanup_list); - - let src = bindgen.src; - - let result_type = match &sig.results[..] { - [] => "void", - [result] => wasm_type(*result), - _ => unreachable!(), - }; - - let camel_name = func.name.to_upper_camel_case(); - - let params = sig - .params - .iter() - .enumerate() - .map(|(i, param)| { - let ty = wasm_type(*param); - format!("{ty} p{i}") - }) - .collect::>() - .join(", "); - - uwrite!( - self.src, - r#" - @Export(name = "{export_name}") - private static {result_type} wasmExport{camel_name}({params}) {{ - {src} - }} - "# - ); - - if abi::guest_export_needs_post_return(self.resolve, func) { - let params = sig - .results - .iter() - .enumerate() - .map(|(i, param)| { - let ty = wasm_type(*param); - format!("{ty} p{i}") - }) - .collect::>() - .join(", "); - - let mut bindgen = FunctionBindgen::new( - self, - "INVALID", - (0..sig.results.len()).map(|i| format!("p{i}")).collect(), - ); - - abi::post_return(bindgen.gen.resolve, func, &mut bindgen, false); - - let src = bindgen.src; - - uwrite!( - self.src, - r#" - @Export(name = "cabi_post_{export_name}") - private static void wasmExport{camel_name}PostReturn({params}) {{ - {src} - }} - "# - ); - } - - if self.gen.opts.generate_stub { - let sig = self.sig_string(func, true); - - uwrite!( - self.stub, - r#" - {sig} {{ - throw new RuntimeException("todo"); - }} - "# - ); - } - } - - fn type_name(&mut self, ty: &Type) -> String { - self.type_name_with_qualifier(ty, false) - } - - fn type_name_with_qualifier(&mut self, ty: &Type, qualifier: bool) -> String { - match ty { - Type::Bool => "boolean".into(), - Type::U8 | Type::S8 => "byte".into(), - Type::U16 | Type::S16 => "short".into(), - Type::U32 | Type::S32 | Type::Char => "int".into(), - Type::U64 | Type::S64 => "long".into(), - Type::F32 => "float".into(), - Type::F64 => "double".into(), - Type::String => "String".into(), - Type::ErrorContext => todo!("error context type name"), - Type::Id(id) => { - let ty = &self.resolve.types[*id]; - match &ty.kind { - TypeDefKind::Type(ty) => self.type_name_with_qualifier(ty, qualifier), - TypeDefKind::List(ty) => { - if is_primitive(ty) { - format!("{}[]", self.type_name(ty)) - } else { - format!("ArrayList<{}>", self.type_name_boxed(ty, qualifier)) - } - } - TypeDefKind::Tuple(tuple) => { - let count = tuple.types.len(); - self.gen.tuple_counts.insert(count); - - let params = if count == 0 { - String::new() - } else { - format!( - "<{}>", - tuple - .types - .iter() - .map(|ty| self.type_name_boxed(ty, qualifier)) - .collect::>() - .join(", ") - ) - }; - - format!("{}Tuple{count}{params}", self.gen.qualifier()) - } - TypeDefKind::Option(ty) => self.type_name_boxed(ty, qualifier), - TypeDefKind::Result(result) => { - self.gen.needs_result = true; - let mut name = |ty: &Option| { - ty.as_ref() - .map(|ty| self.type_name_boxed(ty, qualifier)) - .unwrap_or_else(|| { - self.gen.tuple_counts.insert(0); - - format!("{}Tuple0", self.gen.qualifier()) - }) - }; - let ok = name(&result.ok); - let err = name(&result.err); - - format!("{}Result<{ok}, {err}>", self.gen.qualifier()) - } - _ => { - if let Some(name) = &ty.name { - format!( - "{}{}", - self.qualifier(qualifier, ty), - name.to_upper_camel_case() - ) - } else { - unreachable!() - } - } - } - } - } - } - - fn type_name_boxed(&mut self, ty: &Type, qualifier: bool) -> String { - match ty { - Type::Bool => "Boolean".into(), - Type::U8 | Type::S8 => "Byte".into(), - Type::U16 | Type::S16 => "Short".into(), - Type::U32 | Type::S32 | Type::Char => "Integer".into(), - Type::U64 | Type::S64 => "Long".into(), - Type::F32 => "Float".into(), - Type::F64 => "Double".into(), - Type::Id(id) => { - let def = &self.resolve.types[*id]; - match &def.kind { - TypeDefKind::Type(ty) => self.type_name_boxed(ty, qualifier), - _ => self.type_name_with_qualifier(ty, qualifier), - } - } - _ => self.type_name_with_qualifier(ty, qualifier), - } - } - - fn print_docs(&mut self, docs: &Docs) { - if let Some(docs) = &docs.contents { - let lines = docs - .trim() - .lines() - .map(|line| format!("* {line}")) - .collect::>() - .join("\n"); - - uwrite!( - self.src, - " - /** - {lines} - */ - " - ) - } - } - - fn non_empty_type<'a>(&self, ty: Option<&'a Type>) -> Option<&'a Type> { - if let Some(ty) = ty { - let id = match ty { - Type::Id(id) => *id, - _ => return Some(ty), - }; - match &self.resolve.types[id].kind { - TypeDefKind::Type(t) => self.non_empty_type(Some(t)).map(|_| ty), - TypeDefKind::Record(r) => (!r.fields.is_empty()).then_some(ty), - TypeDefKind::Tuple(t) => (!t.types.is_empty()).then_some(ty), - _ => Some(ty), - } - } else { - None - } - } - - fn sig_string(&mut self, func: &Function, qualifier: bool) -> String { - let name = func.name.to_java_ident(); - - let result_type = match &func.result { - None => "void".into(), - Some(ty) => self.type_name_with_qualifier(ty, qualifier), - }; - - let params = func - .params - .iter() - .map(|(name, ty)| { - let ty = self.type_name_with_qualifier(ty, qualifier); - let name = name.to_java_ident(); - format!("{ty} {name}") - }) - .collect::>() - .join(", "); - - format!("public static {result_type} {name}({params})") - } -} - -impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { - fn resolve(&self) -> &'a Resolve { - self.resolve - } - - fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) { - self.print_docs(docs); - - let name = name.to_upper_camel_case(); - - let parameters = record - .fields - .iter() - .map(|field| { - format!( - "{} {}", - self.type_name(&field.ty), - field.name.to_java_ident() - ) - }) - .collect::>() - .join(", "); - - let assignments = record - .fields - .iter() - .map(|field| { - let name = field.name.to_java_ident(); - format!("this.{name} = {name};") - }) - .collect::>() - .join("\n"); - - let fields = if record.fields.is_empty() { - format!("public static final {name} INSTANCE = new {name}();") - } else { - record - .fields - .iter() - .map(|field| { - format!( - "public final {} {};", - self.type_name(&field.ty), - field.name.to_java_ident() - ) - }) - .collect::>() - .join("\n") - }; - - uwrite!( - self.src, - " - public static final class {name} {{ - {fields} - - public {name}({parameters}) {{ - {assignments} - }} - }} - " - ); - } - - fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) { - _ = (id, name, docs); - todo!() - } - - fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) { - self.print_docs(docs); - - let name = name.to_upper_camel_case(); - - let ty = match flags.repr() { - FlagsRepr::U8 => "byte", - FlagsRepr::U16 => "short", - FlagsRepr::U32(1) => "int", - FlagsRepr::U32(2) => "long", - repr => todo!("flags {repr:?}"), - }; - - let flags = flags - .flags - .iter() - .enumerate() - .map(|(i, flag)| { - let flag_name = flag.name.to_shouty_snake_case(); - let suffix = if matches!(flags.repr(), FlagsRepr::U32(2)) { - "L" - } else { - "" - }; - format!( - "public static final {name} {flag_name} = new {name}(({ty}) (1{suffix} << {i}));" - ) - }) - .collect::>() - .join("\n"); - - uwrite!( - self.src, - " - public static final class {name} {{ - public final {ty} value; - - public {name}({ty} value) {{ - this.value = value; - }} - - {flags} - }} - " - ); - } - - fn type_tuple(&mut self, id: TypeId, _name: &str, _tuple: &Tuple, _docs: &Docs) { - self.type_name(&Type::Id(id)); - } - - fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, docs: &Docs) { - self.print_docs(docs); - - let name = name.to_upper_camel_case(); - let tag_type = int_type(variant.tag()); - - let constructors = variant - .cases - .iter() - .map(|case| { - let case_name = case.name.to_java_ident(); - let tag = case.name.to_shouty_snake_case(); - let (parameter, argument) = if let Some(ty) = self.non_empty_type(case.ty.as_ref()) - { - ( - format!("{} {case_name}", self.type_name(ty)), - case_name.deref(), - ) - } else { - (String::new(), "null") - }; - - format!( - "public static {name} {case_name}({parameter}) {{ - return new {name}({tag}, {argument}); - }} - " - ) - }) - .collect::>() - .join("\n"); - - let accessors = variant - .cases - .iter() - .filter_map(|case| { - self.non_empty_type(case.ty.as_ref()).map(|ty| { - let case_name = case.name.to_upper_camel_case(); - let tag = case.name.to_shouty_snake_case(); - let ty = self.type_name(ty); - format!( - r#"public {ty} get{case_name}() {{ - if (this.tag == {tag}) {{ - return ({ty}) this.value; - }} else {{ - throw new RuntimeException("expected {tag}, got " + this.tag); - }} - }} - "# - ) - }) - }) - .collect::>() - .join("\n"); - - let tags = variant - .cases - .iter() - .enumerate() - .map(|(i, case)| { - let tag = case.name.to_shouty_snake_case(); - format!("public static final {tag_type} {tag} = {i};") - }) - .collect::>() - .join("\n"); - - uwrite!( - self.src, - " - public static final class {name} {{ - public final {tag_type} tag; - private final Object value; - - private {name}({tag_type} tag, Object value) {{ - this.tag = tag; - this.value = value; - }} - - {constructors} - {accessors} - {tags} - }} - " - ); - } - - fn type_option(&mut self, id: TypeId, _name: &str, _payload: &Type, _docs: &Docs) { - self.type_name(&Type::Id(id)); - } - - fn type_result(&mut self, id: TypeId, _name: &str, _result: &Result_, _docs: &Docs) { - self.type_name(&Type::Id(id)); - } - - fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { - self.print_docs(docs); - - let name = name.to_upper_camel_case(); - - let cases = enum_ - .cases - .iter() - .map(|case| case.name.to_shouty_snake_case()) - .collect::>() - .join(", "); - - uwrite!( - self.src, - " - public static enum {name} {{ - {cases} - }} - " - ); - } - - fn type_alias(&mut self, id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { - self.type_name(&Type::Id(id)); - } - - fn type_list(&mut self, id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { - self.type_name(&Type::Id(id)); - } - - fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { - _ = (id, name, ty, docs); - todo!() - } - - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { - _ = (id, name, ty, docs); - todo!() - } - - fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { - unimplemented!(); - } -} - -struct Block { - body: String, - results: Vec, - element: String, - base: String, -} - -struct Cleanup { - address: String, - size: String, - align: usize, -} - -struct BlockStorage { - body: String, - element: String, - base: String, - cleanup: Vec, -} - -struct FunctionBindgen<'a, 'b> { - gen: &'b mut InterfaceGenerator<'a>, - func_name: &'b str, - params: Box<[String]>, - src: String, - locals: Ns, - block_storage: Vec, - blocks: Vec, - payloads: Vec, - cleanup: Vec, - needs_cleanup_list: bool, -} - -impl<'a, 'b> FunctionBindgen<'a, 'b> { - fn new( - gen: &'b mut InterfaceGenerator<'a>, - func_name: &'b str, - params: Box<[String]>, - ) -> FunctionBindgen<'a, 'b> { - Self { - gen, - func_name, - params, - src: String::new(), - locals: Ns::default(), - block_storage: Vec::new(), - blocks: Vec::new(), - payloads: Vec::new(), - cleanup: Vec::new(), - needs_cleanup_list: false, - } - } - - fn lower_variant( - &mut self, - cases: &[(&str, Option)], - lowered_types: &[WasmType], - op: &str, - results: &mut Vec, - ) { - let blocks = self - .blocks - .drain(self.blocks.len() - cases.len()..) - .collect::>(); - - let payloads = self - .payloads - .drain(self.payloads.len() - cases.len()..) - .collect::>(); - - let lowered = lowered_types - .iter() - .map(|_| self.locals.tmp("lowered")) - .collect::>(); - - results.extend(lowered.iter().cloned()); - - let declarations = lowered - .iter() - .zip(lowered_types) - .map(|(lowered, ty)| format!("{} {lowered};", wasm_type(*ty))) - .collect::>() - .join("\n"); - - let cases = cases - .iter() - .zip(blocks) - .zip(payloads) - .enumerate() - .map( - |(i, (((name, ty), Block { body, results, .. }), payload))| { - let payload = if let Some(ty) = self.gen.non_empty_type(ty.as_ref()) { - let ty = self.gen.type_name(ty); - let name = name.to_upper_camel_case(); - - format!("{ty} {payload} = ({op}).get{name}();") - } else { - String::new() - }; - - let assignments = lowered - .iter() - .zip(&results) - .map(|(lowered, result)| format!("{lowered} = {result};\n")) - .collect::>() - .concat(); - - format!( - "case {i}: {{ - {payload} - {body} - {assignments} - break; - }}" - ) - }, - ) - .collect::>() - .join("\n"); - - uwrite!( - self.src, - r#" - {declarations} - - switch (({op}).tag) {{ - {cases} - - default: throw new AssertionError("invalid discriminant: " + ({op}).tag); - }} - "# - ); - } - - fn lift_variant( - &mut self, - ty: &Type, - cases: &[(&str, Option)], - op: &str, - results: &mut Vec, - ) { - let blocks = self - .blocks - .drain(self.blocks.len() - cases.len()..) - .collect::>(); - - let ty = self.gen.type_name(ty); - let generics_position = ty.find('<'); - let lifted = self.locals.tmp("lifted"); - - let cases = cases - .iter() - .zip(blocks) - .enumerate() - .map(|(i, ((case_name, case_ty), Block { body, results, .. }))| { - let payload = if self.gen.non_empty_type(case_ty.as_ref()).is_some() { - results.into_iter().next().unwrap() - } else if generics_position.is_some() { - if let Some(ty) = case_ty.as_ref() { - format!("{}.INSTANCE", self.gen.type_name(ty)) - } else { - format!("{}Tuple0.INSTANCE", self.gen.gen.qualifier()) - } - } else { - String::new() - }; - - let method = case_name.to_java_ident(); - - let call = if let Some(position) = generics_position { - let (ty, generics) = ty.split_at(position); - format!("{ty}.{generics}{method}") - } else { - format!("{ty}.{method}") - }; - - format!( - "case {i}: {{ - {body} - {lifted} = {call}({payload}); - break; - }}" - ) - }) - .collect::>() - .join("\n"); - - uwrite!( - self.src, - r#" - {ty} {lifted}; - - switch ({op}) {{ - {cases} - - default: throw new AssertionError("invalid discriminant: " + ({op})); - }} - "# - ); - - results.push(lifted); - } -} - -impl Bindgen for FunctionBindgen<'_, '_> { - type Operand = String; - - fn emit( - &mut self, - _resolve: &Resolve, - inst: &Instruction<'_>, - operands: &mut Vec, - results: &mut Vec, - ) { - match inst { - Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), - Instruction::I32Const { val } => results.push(val.to_string()), - Instruction::ConstZero { tys } => results.extend(tys.iter().map(|ty| { - match ty { - WasmType::I32 => "0", - WasmType::I64 => "0L", - WasmType::F32 => "0.0F", - WasmType::F64 => "0.0D", - WasmType::Pointer => "0", - WasmType::PointerOrI64 => "0L", - WasmType::Length => "0", - } - .to_owned() - })), - - // TODO: checked - Instruction::U8FromI32 => results.push(format!("(byte) ({})", operands[0])), - Instruction::S8FromI32 => results.push(format!("(byte) ({})", operands[0])), - Instruction::U16FromI32 => results.push(format!("(short) ({})", operands[0])), - Instruction::S16FromI32 => results.push(format!("(short) ({})", operands[0])), - - Instruction::I32FromU8 => results.push(format!("((int) ({})) & 0xFF", operands[0])), - Instruction::I32FromU16 => results.push(format!("((int) ({})) & 0xFFFF", operands[0])), - - Instruction::I32FromS8 | Instruction::I32FromS16 => { - results.push(format!("(int) ({})", operands[0])) - } - - Instruction::CharFromI32 - | Instruction::I32FromChar - | Instruction::U32FromI32 - | Instruction::S32FromI32 - | Instruction::S64FromI64 - | Instruction::U64FromI64 - | Instruction::I32FromU32 - | Instruction::I32FromS32 - | Instruction::I64FromS64 - | Instruction::I64FromU64 - | Instruction::CoreF32FromF32 - | Instruction::CoreF64FromF64 - | Instruction::F32FromCoreF32 - | Instruction::F64FromCoreF64 => results.push(operands[0].clone()), - - Instruction::Bitcasts { casts } => results.extend( - casts - .iter() - .zip(operands) - .map(|(cast, op)| perform_cast(op, cast)), - ), - - Instruction::I32FromBool => { - results.push(format!("({} ? 1 : 0)", operands[0])); - } - Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])), - - // TODO: checked - Instruction::FlagsLower { flags, .. } => match flags_repr(flags) { - Int::U8 | Int::U16 | Int::U32 => { - results.push(format!("({}).value", operands[0])); - } - Int::U64 => { - let op = &operands[0]; - results.push(format!("(int) (({op}).value & 0xffffffffL)")); - results.push(format!("(int) ((({op}).value >>> 32) & 0xffffffffL)")); - } - }, - - Instruction::FlagsLift { flags, ty, .. } => match flags_repr(flags) { - Int::U8 | Int::U16 | Int::U32 => { - results.push(format!( - "new {}(({}) {})", - self.gen.type_name(&Type::Id(*ty)), - int_type(flags_repr(flags)), - operands[0] - )); - } - Int::U64 => { - results.push(format!( - "new {}(((long) ({})) | (((long) ({})) << 32))", - self.gen.type_name(&Type::Id(*ty)), - operands[0], - operands[1] - )); - } - }, - - Instruction::HandleLower { .. } | Instruction::HandleLift { .. } => todo!(), - - Instruction::RecordLower { record, .. } => { - let op = &operands[0]; - for field in record.fields.iter() { - results.push(format!("({op}).{}", field.name.to_java_ident())); - } - } - Instruction::RecordLift { ty, .. } | Instruction::TupleLift { ty, .. } => { - let ops = operands - .iter() - .map(|op| op.to_string()) - .collect::>() - .join(", "); - - results.push(format!("new {}({ops})", self.gen.type_name(&Type::Id(*ty)))); - } - - Instruction::TupleLower { tuple, .. } => { - let op = &operands[0]; - for i in 0..tuple.types.len() { - results.push(format!("({op}).f{i}")); - } - } - - Instruction::VariantPayloadName => { - let payload = self.locals.tmp("payload"); - results.push(payload.clone()); - self.payloads.push(payload); - } - - Instruction::VariantLower { - variant, - results: lowered_types, - .. - } => self.lower_variant( - &variant - .cases - .iter() - .map(|case| (case.name.deref(), case.ty)) - .collect::>(), - lowered_types, - &operands[0], - results, - ), - - Instruction::VariantLift { variant, ty, .. } => self.lift_variant( - &Type::Id(*ty), - &variant - .cases - .iter() - .map(|case| (case.name.deref(), case.ty)) - .collect::>(), - &operands[0], - results, - ), - - Instruction::OptionLower { - results: lowered_types, - payload, - .. - } => { - let some = self.blocks.pop().unwrap(); - let none = self.blocks.pop().unwrap(); - let some_payload = self.payloads.pop().unwrap(); - let none_payload = self.payloads.pop().unwrap(); - - let lowered = lowered_types - .iter() - .map(|_| self.locals.tmp("lowered")) - .collect::>(); - - results.extend(lowered.iter().cloned()); - - let declarations = lowered - .iter() - .zip(lowered_types.iter()) - .map(|(lowered, ty)| format!("{} {lowered};", wasm_type(*ty))) - .collect::>() - .join("\n"); - - let op = &operands[0]; - - let mut block = |ty: Option<&Type>, Block { body, results, .. }, payload| { - let payload = if let Some(ty) = self.gen.non_empty_type(ty) { - let ty = self.gen.type_name(ty); - - format!("{ty} {payload} = ({ty}) ({op});") - } else { - String::new() - }; - - let assignments = lowered - .iter() - .zip(&results) - .map(|(lowered, result)| format!("{lowered} = {result};\n")) - .collect::>() - .concat(); - - format!( - "{payload} - {body} - {assignments}" - ) - }; - - let none = block(None, none, none_payload); - let some = block(Some(payload), some, some_payload); - - uwrite!( - self.src, - r#" - {declarations} - - if (({op}) == null) {{ - {none} - }} else {{ - {some} - }} - "# - ); - } - - Instruction::OptionLift { payload, ty } => { - let some = self.blocks.pop().unwrap(); - let _none = self.blocks.pop().unwrap(); - - let ty = self.gen.type_name(&Type::Id(*ty)); - let lifted = self.locals.tmp("lifted"); - let op = &operands[0]; - - let payload = if self.gen.non_empty_type(Some(*payload)).is_some() { - some.results.into_iter().next().unwrap() - } else { - "null".into() - }; - - let some = some.body; - - uwrite!( - self.src, - r#" - {ty} {lifted}; - - switch ({op}) {{ - case 0: {{ - {lifted} = null; - break; - }} - - case 1: {{ - {some} - {lifted} = {payload}; - break; - }} - - default: throw new AssertionError("invalid discriminant: " + ({op})); - }} - "# - ); - - results.push(lifted); - } - - Instruction::ResultLower { - results: lowered_types, - result, - .. - } => self.lower_variant( - &[("ok", result.ok), ("err", result.err)], - lowered_types, - &operands[0], - results, - ), - - Instruction::ResultLift { result, ty } => self.lift_variant( - &Type::Id(*ty), - &[("ok", result.ok), ("err", result.err)], - &operands[0], - results, - ), - - Instruction::EnumLower { .. } => results.push(format!("{}.ordinal()", operands[0])), - - Instruction::EnumLift { ty, .. } => results.push(format!( - "{}.values()[{}]", - self.gen.type_name(&Type::Id(*ty)), - operands[0] - )), - - Instruction::ListCanonLower { element, realloc } => { - let op = &operands[0]; - let (size, ty) = list_element_info(element); - - // Note that we can only reliably use `Address.ofData` for elements with alignment <= 4 because as - // of this writing TeaVM does not guarantee 64 bit items are aligned on 8 byte boundaries. - if realloc.is_none() && size <= 4 { - results.push(format!("org.teavm.interop.Address.ofData({op}).toInt()")); - } else { - let address = self.locals.tmp("address"); - let ty = ty.to_upper_camel_case(); - - uwrite!( - self.src, - " - org.teavm.interop.Address {address} = Memory.malloc({size} * ({op}).length, {size}); - Memory.put{ty}s({address}, {op}, 0, ({op}).length); - " - ); - - if realloc.is_none() { - self.cleanup.push(Cleanup { - address: format!("{address}.toInt()"), - size: format!("{size} * ({op}).length"), - align: size, - }); - } - - results.push(format!("{address}.toInt()")); - } - results.push(format!("({op}).length")); - } - - Instruction::ListCanonLift { element, .. } => { - let (_, ty) = list_element_info(element); - let ty_upper = ty.to_upper_camel_case(); - let array = self.locals.tmp("array"); - let address = &operands[0]; - let length = &operands[1]; - - uwrite!( - self.src, - " - {ty}[] {array} = new {ty}[{length}]; - Memory.get{ty_upper}s(org.teavm.interop.Address.fromInt({address}), {array}, 0, ({array}).length); - " - ); - - results.push(array); - } - - Instruction::StringLower { realloc } => { - let op = &operands[0]; - let bytes = self.locals.tmp("bytes"); - uwriteln!( - self.src, - "byte[] {bytes} = ({op}).getBytes(StandardCharsets.UTF_8);" - ); - - if realloc.is_none() { - results.push(format!("org.teavm.interop.Address.ofData({bytes}).toInt()")); - } else { - let address = self.locals.tmp("address"); - - uwrite!( - self.src, - " - org.teavm.interop.Address {address} = Memory.malloc({bytes}.length, 1); - Memory.putBytes({address}, {bytes}, 0, {bytes}.length); - " - ); - - results.push(format!("{address}.toInt()")); - } - results.push(format!("{bytes}.length")); - } - - Instruction::StringLift { .. } => { - let bytes = self.locals.tmp("bytes"); - let address = &operands[0]; - let length = &operands[1]; - - uwrite!( - self.src, - " - byte[] {bytes} = new byte[{length}]; - Memory.getBytes(org.teavm.interop.Address.fromInt({address}), {bytes}, 0, {length}); - " - ); - - results.push(format!("new String({bytes}, StandardCharsets.UTF_8)")); - } - - Instruction::ListLower { element, realloc } => { - let Block { - body, - results: block_results, - element: block_element, - base, - } = self.blocks.pop().unwrap(); - assert!(block_results.is_empty()); - - let op = &operands[0]; - let size = self.gen.gen.sizes.size(element); - let align = self.gen.gen.sizes.align(element); - let address = self.locals.tmp("address"); - let ty = self.gen.type_name(element); - let index = self.locals.tmp("index"); - - uwrite!( - self.src, - " - int {address} = Memory.malloc(({op}).size() * {size}, {align}).toInt(); - for (int {index} = 0; {index} < ({op}).size(); ++{index}) {{ - {ty} {block_element} = ({op}).get({index}); - int {base} = {address} + ({index} * {size}); - {body} - }} - ", - align = align.align_wasm32(), - size = size.size_wasm32() - ); - - if realloc.is_none() { - self.cleanup.push(Cleanup { - address: address.clone(), - size: format!("({op}).size() * {size}", size = size.size_wasm32()), - align: align.align_wasm32(), - }); - } - - results.push(address); - results.push(format!("({op}).size()")); - } - - Instruction::ListLift { element, .. } => { - let Block { - body, - results: block_results, - base, - .. - } = self.blocks.pop().unwrap(); - let address = &operands[0]; - let length = &operands[1]; - let array = self.locals.tmp("array"); - let ty = self.gen.type_name(element); - let size = self.gen.gen.sizes.size(element).size_wasm32(); - let align = self.gen.gen.sizes.align(element).align_wasm32(); - let index = self.locals.tmp("index"); - - let result = match &block_results[..] { - [result] => result, - _ => todo!("result count == {}", results.len()), - }; - - uwrite!( - self.src, - " - ArrayList<{ty}> {array} = new ArrayList<>({length}); - for (int {index} = 0; {index} < ({length}); ++{index}) {{ - int {base} = ({address}) + ({index} * {size}); - {body} - {array}.add({result}); - }} - Memory.free(org.teavm.interop.Address.fromInt({address}), ({length}) * {size}, {align}); - " - ); - - results.push(array); - } - - Instruction::IterElem { .. } => { - results.push(self.block_storage.last().unwrap().element.clone()) - } - - Instruction::IterBasePointer => { - results.push(self.block_storage.last().unwrap().base.clone()) - } - - Instruction::CallWasm { sig, .. } => { - let assignment = match &sig.results[..] { - [result] => { - let ty = wasm_type(*result); - let result = self.locals.tmp("result"); - let assignment = format!("{ty} {result} = "); - results.push(result); - assignment - } - - [] => String::new(), - - _ => unreachable!(), - }; - - let func_name = self.func_name.to_upper_camel_case(); - - let operands = operands.join(", "); - - uwriteln!(self.src, "{assignment} wasmImport{func_name}({operands});"); - } - - Instruction::CallInterface { func, .. } => { - let (assignment, destructure) = match &func.result { - None => (String::new(), String::new()), - Some(ty) => { - let ty = self.gen.type_name(ty); - let result = self.locals.tmp("result"); - let assignment = format!("{ty} {result} = "); - results.push(result); - (assignment, String::new()) - } - }; - - let module = self.gen.name; - let name = func.name.to_java_ident(); - - let args = operands.join(", "); - - uwrite!( - self.src, - " - {assignment}{module}Impl.{name}({args}); - {destructure} - " - ); - } - - Instruction::Return { amt, .. } => { - for Cleanup { - address, - size, - align, - } in &self.cleanup - { - uwriteln!( - self.src, - "Memory.free(org.teavm.interop.Address.fromInt({address}), {size}, {align});" - ); - } - - if self.needs_cleanup_list { - uwrite!( - self.src, - " - for ({}Cleanup cleanup : cleanupList) {{ - Memory.free(org.teavm.interop.Address.fromInt(cleanup.address), cleanup.size, cleanup.align); - }} - ", - self.gen.gen.qualifier() - ); - } - - match *amt { - 0 => (), - 1 => uwriteln!(self.src, "return {};", operands[0]), - count => { - let results = operands.join(", "); - uwriteln!( - self.src, - "return new {}Tuple{count}<>({results});", - self.gen.gen.qualifier() - ) - } - } - } - - Instruction::I32Load { offset } - | Instruction::PointerLoad { offset } - | Instruction::LengthLoad { offset } => results.push(format!( - "org.teavm.interop.Address.fromInt(({}) + {offset}).getInt()", - operands[0], - offset = offset.size_wasm32() - )), - - Instruction::I32Load8U { offset } => results.push(format!( - "(((int) org.teavm.interop.Address.fromInt(({}) + {offset}).getByte()) & 0xFF)", - operands[0], - offset = offset.size_wasm32() - )), - - Instruction::I32Load8S { offset } => results.push(format!( - "((int) org.teavm.interop.Address.fromInt(({}) + {offset}).getByte())", - operands[0], - offset = offset.size_wasm32() - )), - - Instruction::I32Load16U { offset } => results.push(format!( - "(((int) org.teavm.interop.Address.fromInt(({}) + {offset}).getShort()) & 0xFFFF)", - operands[0], - offset = offset.size_wasm32() - )), - - Instruction::I32Load16S { offset } => results.push(format!( - "((int) org.teavm.interop.Address.fromInt(({}) + {offset}).getShort())", - operands[0], - offset = offset.size_wasm32() - )), - - Instruction::I64Load { offset } => results.push(format!( - "org.teavm.interop.Address.fromInt(({}) + {offset}).getLong()", - operands[0], - offset = offset.size_wasm32() - )), - - Instruction::F32Load { offset } => results.push(format!( - "org.teavm.interop.Address.fromInt(({}) + {offset}).getFloat()", - operands[0], - offset = offset.size_wasm32() - )), - - Instruction::F64Load { offset } => results.push(format!( - "org.teavm.interop.Address.fromInt(({}) + {offset}).getDouble()", - operands[0], - offset = offset.size_wasm32() - )), - - Instruction::I32Store { offset } - | Instruction::PointerStore { offset } - | Instruction::LengthStore { offset } => uwriteln!( - self.src, - "org.teavm.interop.Address.fromInt(({}) + {offset}).putInt({});", - operands[1], - operands[0], - offset = offset.size_wasm32() - ), - - Instruction::I32Store8 { offset } => uwriteln!( - self.src, - "org.teavm.interop.Address.fromInt(({}) + {offset}).putByte((byte) ({}));", - operands[1], - operands[0], - offset = offset.size_wasm32() - ), - - Instruction::I32Store16 { offset } => uwriteln!( - self.src, - "org.teavm.interop.Address.fromInt(({}) + {offset}).putShort((short) ({}));", - operands[1], - operands[0], - offset = offset.size_wasm32() - ), - - Instruction::I64Store { offset } => uwriteln!( - self.src, - "org.teavm.interop.Address.fromInt(({}) + {offset}).putLong({});", - operands[1], - operands[0], - offset = offset.size_wasm32() - ), - - Instruction::F32Store { offset } => uwriteln!( - self.src, - "org.teavm.interop.Address.fromInt(({}) + {offset}).putFloat({});", - operands[1], - operands[0], - offset = offset.size_wasm32() - ), - - Instruction::F64Store { offset } => uwriteln!( - self.src, - "org.teavm.interop.Address.fromInt(({}) + {offset}).putDouble({});", - operands[1], - operands[0], - offset = offset.size_wasm32() - ), - - Instruction::Malloc { .. } => unimplemented!(), - - Instruction::GuestDeallocate { size, align } => { - uwriteln!( - self.src, - "Memory.free(org.teavm.interop.Address.fromInt({}), {size}, {align});", - operands[0], - size = size.size_wasm32(), - align = align.align_wasm32() - ) - } - - Instruction::GuestDeallocateString => uwriteln!( - self.src, - "Memory.free(org.teavm.interop.Address.fromInt({}), {}, 1);", - operands[0], - operands[1] - ), - - Instruction::GuestDeallocateVariant { blocks } => { - let cases = self - .blocks - .drain(self.blocks.len() - blocks..) - .enumerate() - .map(|(i, Block { body, results, .. })| { - assert!(results.is_empty()); - - format!( - "case {i}: {{ - {body} - break; - }}" - ) - }) - .collect::>() - .join("\n"); - - let op = &operands[0]; - - uwrite!( - self.src, - " - switch ({op}) {{ - {cases} - }} - " - ); - } - - Instruction::GuestDeallocateList { element } => { - let Block { - body, - results, - base, - .. - } = self.blocks.pop().unwrap(); - assert!(results.is_empty()); - - let address = &operands[0]; - let length = &operands[1]; - - let size = self.gen.gen.sizes.size(element).size_wasm32(); - let align = self.gen.gen.sizes.align(element).align_wasm32(); - - if !body.trim().is_empty() { - let index = self.locals.tmp("index"); - - uwrite!( - self.src, - " - for (int {index} = 0; {index} < ({length}); ++{index}) {{ - int {base} = ({address}) + ({index} * {size}); - {body} - }} - " - ); - } - - uwriteln!( - self.src, - "Memory.free(org.teavm.interop.Address.fromInt({address}), ({length}) * {size}, {align});" - ); - } - - Instruction::Flush { amt } => { - results.extend(operands.iter().take(*amt).map(|v| v.clone())); - } - - Instruction::AsyncPostCallInterface { .. } - | Instruction::AsyncCallReturn { .. } - | Instruction::FutureLower { .. } - | Instruction::FutureLift { .. } - | Instruction::StreamLower { .. } - | Instruction::StreamLift { .. } - | Instruction::ErrorContextLower { .. } - | Instruction::ErrorContextLift { .. } - | Instruction::AsyncCallWasm { .. } => todo!(), - } - } - - fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { - self.gen.gen.return_area_size = self.gen.gen.return_area_size.max(size); - self.gen.gen.return_area_align = self.gen.gen.return_area_align.max(align); - format!("{}RETURN_AREA", self.gen.gen.qualifier()) - } - - fn push_block(&mut self) { - self.block_storage.push(BlockStorage { - body: mem::take(&mut self.src), - element: self.locals.tmp("element"), - base: self.locals.tmp("base"), - cleanup: mem::take(&mut self.cleanup), - }); - } - - fn finish_block(&mut self, operands: &mut Vec) { - let BlockStorage { - body, - element, - base, - cleanup, - } = self.block_storage.pop().unwrap(); - - if !self.cleanup.is_empty() { - self.needs_cleanup_list = true; - - for Cleanup { - address, - size, - align, - } in &self.cleanup - { - uwriteln!( - self.src, - "cleanupList.add(new {}Cleanup({address}, {size}, {align}));", - self.gen.gen.qualifier() - ); - } - } - - self.cleanup = cleanup; - - self.blocks.push(Block { - body: mem::replace(&mut self.src, body), - results: mem::take(operands), - element, - base, - }); - } - - fn sizes(&self) -> &SizeAlign { - &self.gen.gen.sizes - } - - fn is_list_canonical(&self, _resolve: &Resolve, element: &Type) -> bool { - is_primitive(element) - } -} - -fn perform_cast(op: &str, cast: &Bitcast) -> String { - match cast { - Bitcast::I32ToF32 => { - format!("Float.intBitsToFloat({op})") - } - Bitcast::I64ToF32 => format!("Float.intBitsToFloat((int) ({op}))"), - Bitcast::F32ToI32 => { - format!("Float.floatToIntBits({op})") - } - Bitcast::F32ToI64 => format!("(long) Float.floatToIntBits({op})"), - Bitcast::I64ToF64 => { - format!("Double.longBitsToDouble({op})") - } - Bitcast::F64ToI64 => { - format!("Double.doubleToLongBits({op})") - } - Bitcast::I32ToI64 => format!("(long) ({op})"), - Bitcast::I64ToI32 => format!("(int) ({op})"), - Bitcast::I64ToP64 => format!("{op}"), - Bitcast::P64ToI64 => format!("{op}"), - Bitcast::LToI64 | Bitcast::PToP64 => format!("(long) ({op})"), - Bitcast::I64ToL | Bitcast::P64ToP => format!("(int) ({op})"), - Bitcast::I32ToP - | Bitcast::PToI32 - | Bitcast::I32ToL - | Bitcast::LToI32 - | Bitcast::LToP - | Bitcast::PToL - | Bitcast::None => op.to_owned(), - - Bitcast::Sequence(sequence) => { - let [first, second] = &**sequence; - perform_cast(&perform_cast(op, first), second) - } - } -} - -fn int_type(int: Int) -> &'static str { - match int { - Int::U8 => "byte", - Int::U16 => "short", - Int::U32 => "int", - Int::U64 => "long", - } -} - -fn wasm_type(ty: WasmType) -> &'static str { - match ty { - WasmType::I32 => "int", - WasmType::I64 => "long", - WasmType::F32 => "float", - WasmType::F64 => "double", - WasmType::Pointer => "int", - WasmType::PointerOrI64 => "long", - WasmType::Length => "int", - } -} - -fn flags_repr(flags: &Flags) -> Int { - match flags.repr() { - FlagsRepr::U8 => Int::U8, - FlagsRepr::U16 => Int::U16, - FlagsRepr::U32(1) => Int::U32, - FlagsRepr::U32(2) => Int::U64, - repr => panic!("unimplemented flags {repr:?}"), - } -} - -fn list_element_info(ty: &Type) -> (usize, &'static str) { - match ty { - Type::U8 | Type::S8 => (1, "byte"), - Type::U16 | Type::S16 => (2, "short"), - Type::U32 | Type::S32 => (4, "int"), - Type::U64 | Type::S64 => (8, "long"), - Type::F32 => (4, "float"), - Type::F64 => (8, "double"), - _ => unreachable!(), - } -} - -fn indent(code: &str) -> String { - let mut indented = String::with_capacity(code.len()); - let mut indent = 0; - let mut was_empty = false; - for line in code.lines() { - let trimmed = line.trim(); - if trimmed.is_empty() { - if was_empty { - continue; - } - was_empty = true; - } else { - was_empty = false; - } - - if trimmed.starts_with('}') { - indent -= 1; - } - indented.extend(iter::repeat(' ').take(indent * 4)); - indented.push_str(trimmed); - if trimmed.ends_with('{') { - indent += 1; - } - indented.push('\n'); - } - indented -} - -fn is_primitive(ty: &Type) -> bool { - matches!( - ty, - Type::U8 - | Type::S8 - | Type::U16 - | Type::S16 - | Type::U32 - | Type::S32 - | Type::U64 - | Type::S64 - | Type::F32 - | Type::F64 - ) -} - -fn world_name(resolve: &Resolve, world: WorldId) -> String { - format!( - "wit.worlds.{}", - resolve.worlds[world].name.to_upper_camel_case() - ) -} - -fn interface_name(resolve: &Resolve, name: &WorldKey, direction: Direction) -> String { - let pkg = match name { - WorldKey::Name(_) => None, - WorldKey::Interface(id) => { - let pkg = resolve.interfaces[*id].package.unwrap(); - Some(resolve.packages[pkg].name.clone()) - } - }; - - let name = match name { - WorldKey::Name(name) => name, - WorldKey::Interface(id) => resolve.interfaces[*id].name.as_ref().unwrap(), - } - .to_upper_camel_case(); - - format!( - "wit.{}.{}{name}", - match direction { - Direction::Import => "imports", - Direction::Export => "exports", - }, - if let Some(name) = &pkg { - format!( - "{}.{}.", - name.namespace.to_java_ident(), - name.name.to_java_ident() - ) - } else { - String::new() - } - ) -} - -fn split_qualified_name(name: &str) -> (String, &str) { - let tokens = name.split('.').collect::>(); - - let package = tokens - .iter() - .copied() - .take(tokens.len() - 1) - .collect::>() - .join("."); - - let name = tokens.last().unwrap(); - - (package, name) -} - -trait ToJavaIdent: ToOwned { - fn to_java_ident(&self) -> Self::Owned; -} - -impl ToJavaIdent for str { - fn to_java_ident(&self) -> String { - // Escape Java keywords - // Source: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html - match self { - "abstract" | "continue" | "for" | "new" | "switch" | "assert" | "default" | "goto" - | "package" | "synchronized" | "boolean" | "do" | "if" | "private" | "this" - | "break" | "double" | "implements" | "protected" | "throw" | "byte" | "else" - | "import" | "public" | "throws" | "case" | "enum" | "instanceof" | "return" - | "transient" | "catch" | "extends" | "int" | "short" | "try" | "char" | "final" - | "interface" | "static" | "void" | "class" | "finally" | "long" | "strictfp" - | "volatile" | "const" | "float" | "native" | "super" | "while" => format!("{self}_"), - _ => self.to_lower_camel_case(), - } - } -} diff --git a/crates/teavm-java/tests/Main.java b/crates/teavm-java/tests/Main.java deleted file mode 100644 index 25a097deb..000000000 --- a/crates/teavm-java/tests/Main.java +++ /dev/null @@ -1,3 +0,0 @@ -public class Main { - public static void main(String[] args) {} -} diff --git a/crates/teavm-java/tests/codegen.rs b/crates/teavm-java/tests/codegen.rs deleted file mode 100644 index ab637cf04..000000000 --- a/crates/teavm-java/tests/codegen.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; - -macro_rules! codegen_test { - // todo: implement resource support and then remove the following lines: - (resources $name:tt $test:tt) => {}; - (resource_alias $name:tt $test:tt) => {}; - (return_resource_from_export $name:tt $test:tt) => {}; - (import_and_export_resource $name:tt $test:tt) => {}; - (import_and_export_resource_alias $name:tt $test:tt) => {}; - (resources_with_lists $name:tt $test:tt) => {}; - (resource_local_alias $name:tt $test:tt) => {}; - (resource_local_alias_borrow $name:tt $test:tt) => {}; - (resource_local_alias_borrow_import $name:tt $test:tt) => {}; - (resource_borrow_in_record $name:tt $test:tt) => {}; - (resource_borrow_in_record_export $name:tt $test:tt) => {}; - (resource_own_in_other_interface $name:tt $test:tt) => {}; - (same_names5 $name:tt $test:tt) => {}; - (resources_in_aggregates $name:tt $test:tt) => {}; - (issue668 $name:tt $test:tt) => {}; - (multiversion $name:tt $test:tt) => {}; - (wasi_cli $name:tt $test:tt) => {}; - (wasi_clocks $name:tt $test:tt) => {}; - (wasi_filesystem $name:tt $test:tt) => {}; - (wasi_http $name:tt $test:tt) => {}; - (wasi_io $name:tt $test:tt) => {}; - (issue929 $name:tt $test:tt) => {}; - (issue929_no_import $name:tt $test:tt) => {}; - (issue929_no_export $name:tt $test:tt) => {}; - (issue929_only_methods $name:tt $test:tt) => {}; - - // TODO: implement support for stream, future, and error-context, and then - // remove these lines: - (streams $name:tt $test:tt) => {}; - (futures $name:tt $test:tt) => {}; - (resources_with_streams $name:tt $test:tt) => {}; - (resources_with_futures $name:tt $test:tt) => {}; - (error_context $name:tt $test:tt) => {}; - - ($id:ident $name:tt $test:tt) => { - #[test] - fn $id() { - test_helpers::run_world_codegen_test( - "guest-teavm-java", - $test.as_ref(), - |resolve, world, files| { - wit_bindgen_teavm_java::Opts { - generate_stub: true, - } - .build() - .generate(resolve, world, files) - .unwrap() - }, - verify, - ) - } - }; -} -test_helpers::codegen_tests!(); - -fn verify(dir: &Path, _name: &str) { - // Derived from `test_helpers::test_directory` - const DEPTH_FROM_TARGET_DIR: u32 = 3; - - let base_dir = { - let mut dir = dir.to_owned(); - for _ in 0..DEPTH_FROM_TARGET_DIR { - dir.pop(); - } - dir - }; - - let teavm_interop_jar = base_dir.join("teavm-interop-0.2.8.jar"); - - if !teavm_interop_jar.is_file() { - panic!("please run ci/download-teavm.sh prior to running the Java tests") - } - - let mut files = Vec::new(); - move_java_files(&dir.join("wit"), &dir.join("src/main/java/wit"), &mut files); - fs::remove_dir_all(&dir.join("wit")).unwrap(); - - let mut cmd = Command::new("javac"); - cmd.arg("-cp") - .arg(&teavm_interop_jar) - .arg("-d") - .arg("target/classes"); - - for file in files { - cmd.arg(file); - } - - test_helpers::run_command(&mut cmd); -} - -fn move_java_files(src: &Path, dst: &Path, files: &mut Vec) { - if src.is_dir() { - for entry in fs::read_dir(src).unwrap() { - let path = entry.unwrap().path(); - move_java_files(&path, &dst.join(path.strip_prefix(src).unwrap()), files); - } - } else if let Some("java") = src.extension().map(|ext| ext.to_str().unwrap()) { - fs::create_dir_all(dst.parent().unwrap()).unwrap(); - fs::rename(src, dst).unwrap(); - files.push(dst.to_owned()); - } -} diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index c6e0b589b..92c4503f2 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -47,14 +47,6 @@ enum Opt { args: Common, }, - /// Generates bindings for TeaVM-based Java guest modules. - #[cfg(feature = "teavm-java")] - TeavmJava { - #[clap(flatten)] - opts: wit_bindgen_teavm_java::Opts, - #[clap(flatten)] - args: Common, - }, /// Generates bindings for TinyGo-based Go guest modules. #[cfg(feature = "go")] TinyGo { @@ -131,8 +123,6 @@ fn main() -> Result<()> { Opt::C { opts, args } => (opts.build(), args), #[cfg(feature = "rust")] Opt::Rust { opts, args } => (opts.build(), args), - #[cfg(feature = "teavm-java")] - Opt::TeavmJava { opts, args } => (opts.build(), args), #[cfg(feature = "go")] Opt::TinyGo { opts, args } => (opts.build(), args), #[cfg(feature = "csharp")] diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 7c801b011..e86fbac70 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -346,157 +346,6 @@ fn tests(name: &str, dir_name: &str) -> Result> { result.push(component_path); } - #[cfg(feature = "teavm-java")] - if !java.is_empty() { - let (resolve, world) = resolve_wit_dir(&dir); - const DEPTH_FROM_TARGET_DIR: u32 = 2; - - let base_dir = { - let mut dir = out_dir.to_owned(); - for _ in 0..DEPTH_FROM_TARGET_DIR { - dir.pop(); - } - dir - }; - - let teavm_interop_jar = base_dir.join("teavm-interop-0.2.8.jar"); - let teavm_cli_jar = base_dir.join("teavm-cli-0.2.8.jar"); - if !(teavm_interop_jar.is_file() && teavm_cli_jar.is_file()) { - panic!("please run ci/download-teavm.sh prior to running the Java tests") - } - - let world_name = &resolve.worlds[world].name; - let out_dir = out_dir.join(format!("java-{}", world_name)); - drop(fs::remove_dir_all(&out_dir)); - let java_dir = out_dir.join("src/main/java"); - let mut files = Default::default(); - - wit_bindgen_teavm_java::Opts::default() - .build() - .generate(&resolve, world, &mut files) - .unwrap(); - - let mut dst_files = Vec::new(); - - fs::create_dir_all(&java_dir).unwrap(); - for (file, contents) in files.iter() { - let dst = java_dir.join(file); - fs::create_dir_all(dst.parent().unwrap()).unwrap(); - fs::write(&dst, contents).unwrap(); - dst_files.push(dst); - } - - for java_impl in java { - let dst = java_dir.join( - java_impl - .file_name() - .unwrap() - .to_str() - .unwrap() - .replace('_', "/"), - ); - fs::copy(&java_impl, &dst).unwrap(); - dst_files.push(dst); - } - - let main = java_dir.join("Main.java"); - - fs::write( - &main, - include_bytes!("../../crates/teavm-java/tests/Main.java"), - ) - .unwrap(); - - dst_files.push(main); - - let mut cmd = Command::new("javac"); - cmd.arg("-cp") - .arg(&teavm_interop_jar) - .arg("-d") - .arg(out_dir.join("target/classes")); - - for file in &dst_files { - cmd.arg(file); - } - - println!("{cmd:?}"); - let output = match cmd.output() { - Ok(output) => output, - Err(e) => panic!("failed to run javac: {}", e), - }; - - if !output.status.success() { - println!("status: {}", output.status); - println!("stdout: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stderr)); - panic!("failed to build"); - } - - let mut cmd = Command::new("java"); - cmd.arg("-jar") - .arg(&teavm_cli_jar) - .arg("-p") - .arg(out_dir.join("target/classes")) - .arg("-d") - .arg(out_dir.join("target/generated/wasm/teavm-wasm")) - .arg("-t") - .arg("wasm") - .arg("-g") - .arg("-O") - .arg("1"); - - for file in dst_files { - cmd.arg("--preserve-class").arg( - file.strip_prefix(&java_dir) - .unwrap() - .to_str() - .unwrap() - .strip_suffix(".java") - .unwrap() - .replace('/', "."), - ); - } - - cmd.arg("Main"); - - println!("{cmd:?}"); - let output = match cmd.output() { - Ok(output) => output, - Err(e) => panic!("failed to run teavm: {}", e), - }; - - if !output.status.success() { - println!("status: {}", output.status); - println!("stdout: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stderr)); - panic!("failed to build"); - } - - let out_wasm = out_dir.join("target/generated/wasm/teavm-wasm/classes.wasm"); - - // Translate the canonical ABI module into a component. - let module = fs::read(&out_wasm).expect("failed to read wasm file"); - let component = ComponentEncoder::default() - .module(module.as_slice()) - .expect("pull custom sections from module") - .validate(true) - .adapter("wasi_snapshot_preview1", &wasi_adapter) - .expect("adapter failed to get loaded") - .encode() - .expect(&format!( - "module {out_wasm:?} can be translated to a component", - )); - let component_path = - out_dir.join("target/generated/wasm/teavm-wasm/classes.component.wasm"); - fs::write(&component_path, component).expect("write component to disk"); - - result.push(component_path); - } - #[cfg(feature = "csharp-mono")] if cfg!(windows) && !c_sharp.is_empty() { let (resolve, world) = resolve_wit_dir(&dir); From 976882ff7b70324ab8c9f6a707752e690446596e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Mar 2025 14:31:09 -0500 Subject: [PATCH 08/14] Test out using `--release` on tests in CI (#1201) I have a hunch this is why C# is so slow (the modules are big and there's a lot of them and unoptimized Cranelift is very slow). --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 69d759fe3..2dfbe76a3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -113,7 +113,8 @@ jobs: -p wit-bindgen-cli \ -p wit-bindgen-${{ matrix.lang }} \ --no-default-features \ - --features ${{ matrix.lang }} + --features ${{ matrix.lang }} \ + --release test_unit: name: Crate Unit Tests From 07dfa1d7d2db6468c1bc6dd92fed168a92e1e00b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Mar 2025 17:23:42 -0500 Subject: [PATCH 09/14] Remove teavm-java test source files (#1203) Forgotten from #1202 --- .../wit_exports_test_lists_TestImpl.java | 112 ------------- tests/runtime/lists/wit_worlds_ListsImpl.java | 151 ------------------ .../wit_exports_test_numbers_TestImpl.java | 57 ------- .../numbers/wit_worlds_NumbersImpl.java | 65 -------- .../wit_exports_test_records_TestImpl.java | 37 ----- .../records/wit_worlds_RecordsImpl.java | 72 --------- tests/runtime/smoke/wit_worlds_SmokeImpl.java | 7 - .../wit_exports_test_variants_TestImpl.java | 49 ------ .../variants/wit_worlds_VariantsImpl.java | 115 ------------- 9 files changed, 665 deletions(-) delete mode 100644 tests/runtime/lists/wit_exports_test_lists_TestImpl.java delete mode 100644 tests/runtime/lists/wit_worlds_ListsImpl.java delete mode 100644 tests/runtime/numbers/wit_exports_test_numbers_TestImpl.java delete mode 100644 tests/runtime/numbers/wit_worlds_NumbersImpl.java delete mode 100644 tests/runtime/records/wit_exports_test_records_TestImpl.java delete mode 100644 tests/runtime/records/wit_worlds_RecordsImpl.java delete mode 100644 tests/runtime/smoke/wit_worlds_SmokeImpl.java delete mode 100644 tests/runtime/variants/wit_exports_test_variants_TestImpl.java delete mode 100644 tests/runtime/variants/wit_worlds_VariantsImpl.java diff --git a/tests/runtime/lists/wit_exports_test_lists_TestImpl.java b/tests/runtime/lists/wit_exports_test_lists_TestImpl.java deleted file mode 100644 index 09f188f9c..000000000 --- a/tests/runtime/lists/wit_exports_test_lists_TestImpl.java +++ /dev/null @@ -1,112 +0,0 @@ -package wit.exports.test.lists; - -import static wit.worlds.ListsImpl.expect; - -import java.util.ArrayList; - -import wit.worlds.Lists.Tuple2; -import wit.worlds.Lists.Tuple3; - -public class TestImpl { - public static void emptyListParam(byte[] a) { - expect(a.length == 0); - } - - public static void emptyStringParam(String a) { - expect(a.length() == 0); - } - - public static byte[] emptyListResult() { - return new byte[0]; - } - - public static String emptyStringResult() { - return ""; - } - - public static void listParam(byte[] a) { - expect(a.length == 4); - expect(a[0] == 1); - expect(a[1] == 2); - expect(a[2] == 3); - expect(a[3] == 4); - } - - public static void listParam2(String a) { - expect(a.equals("foo")); - } - - public static void listParam3(ArrayList a) { - expect(a.size() == 3); - expect(a.get(0).equals("foo")); - expect(a.get(1).equals("bar")); - expect(a.get(2).equals("baz")); - } - - public static void listParam4(ArrayList> a) { - expect(a.size() == 2); - expect(a.get(0).size() == 2); - expect(a.get(1).size() == 1); - - expect(a.get(0).get(0).equals("foo")); - expect(a.get(0).get(1).equals("bar")); - expect(a.get(1).get(0).equals("baz")); - } - - public static void listParam5(ArrayList> a) { - expect(a.size() == 2); - expect(a.get(0).f0 == 1); - expect(a.get(0).f1 == 2); - expect(a.get(0).f2 == 3); - expect(a.get(1).f0 == 4); - expect(a.get(1).f1 == 5); - expect(a.get(1).f2 == 6); - } - - public static void listParamLarge(ArrayList a) { - expect(a.size() == 1000); - } - - public static byte[] listResult() { - return new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5 }; - } - - public static String listResult2() { - return "hello!"; - } - - public static ArrayList listResult3() { - return new ArrayList() {{ - add("hello,"); - add("world!"); - }}; - } - - public static byte[] listRoundtrip(byte[] a) { - return a; - } - - public static String stringRoundtrip(String a) { - return a; - } - - public static Tuple2 listMinmax8(byte[] a, byte[] b) { - return new Tuple2<>(a, b); - } - - public static Tuple2 listMinmax16(short[] a, short[] b) { - return new Tuple2<>(a, b); - } - - public static Tuple2 listMinmax32(int[] a, int[] b) { - return new Tuple2<>(a, b); - } - - public static Tuple2 listMinmax64(long[] a, long[] b) { - return new Tuple2<>(a, b); - } - - public static Tuple2 listMinmaxFloat(float[] a, double[] b) { - return new Tuple2<>(a, b); - } -} diff --git a/tests/runtime/lists/wit_worlds_ListsImpl.java b/tests/runtime/lists/wit_worlds_ListsImpl.java deleted file mode 100644 index a0b847a9e..000000000 --- a/tests/runtime/lists/wit_worlds_ListsImpl.java +++ /dev/null @@ -1,151 +0,0 @@ -package wit.worlds; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.ArrayList; - -import wit.imports.test.lists.Test; -import wit.worlds.Lists.Tuple2; - -public class ListsImpl { - public static int allocatedBytes() { - return 0; - } - - public static void testImports() { - Test.emptyListParam(new byte[0]); - - Test.emptyStringParam(""); - - { - byte[] result = Test.emptyListResult(); - expect(result.length == 0); - } - - { - String result = Test.emptyStringResult(); - expect(result.length() == 0); - } - - Test.listParam(new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4 }); - - Test.listParam2("foo"); - - Test.listParam3(new ArrayList() {{ - add("foo"); - add("bar"); - add("baz"); - }}); - - Test.listParam4(new ArrayList>() {{ - add(new ArrayList() {{ - add("foo"); - add("bar"); - }}); - add(new ArrayList() {{ - add("baz"); - }}); - }}); - - { - byte[] result = Test.listResult(); - expect(result.length == 5); - expect(result[0] == (byte) 1); - expect(result[1] == (byte) 2); - expect(result[2] == (byte) 3); - expect(result[3] == (byte) 4); - expect(result[4] == (byte) 5); - } - - { - String result = Test.listResult2(); - expect(result.equals("hello!")); - } - - { - ArrayList result = Test.listResult3(); - expect(result.size() == 2); - expect(result.get(0).equals("hello,")); - expect(result.get(1).equals("world!")); - } - - for (String s : new String[] { "x", "", "hello", "hello ⚑ world" }) { - String result = Test.stringRoundtrip(s); - expect(result.equals(s)); - - byte[] bytes = s.getBytes(StandardCharsets.UTF_8); - expect(Arrays.equals(bytes, Test.listRoundtrip(bytes))); - } - - { - Tuple2 result = Test.listMinmax8 - (new byte[] { (byte) 0, (byte) 0xFF }, new byte[] { (byte) 0x80, (byte) 0x7F }); - - expect(result.f0.length == 2 && result.f0[0] == (byte) 0 && result.f0[1] == (byte) 0xFF); - expect(result.f1.length == 2 && result.f1[0] == (byte) 0x80 && result.f1[1] == (byte) 0x7F); - } - - { - Tuple2 result = Test.listMinmax16 - (new short[] { (short) 0, (short) 0xFFFF }, new short[] { (short) 0x8000, (short) 0x7FFF }); - - expect(result.f0.length == 2 && result.f0[0] == (short) 0 && result.f0[1] == (short) 0xFFFF); - expect(result.f1.length == 2 && result.f1[0] == (short) 0x8000 && result.f1[1] == (short) 0x7FFF); - } - - { - Tuple2 result = Test.listMinmax32 - (new int[] { 0, 0xFFFFFFFF }, new int[] { 0x80000000, 0x7FFFFFFF }); - - expect(result.f0.length == 2 && result.f0[0] == 0 && result.f0[1] == 0xFFFFFFFF); - expect(result.f1.length == 2 && result.f1[0] == 0x80000000 && result.f1[1] == 0x7FFFFFFF); - } - - { - Tuple2 result = Test.listMinmax64 - (new long[] { 0, 0xFFFFFFFFFFFFFFFFL }, new long[] { 0x8000000000000000L, 0x7FFFFFFFFFFFFFFFL }); - - expect(result.f0.length == 2 - && result.f0[0] == 0 - && result.f0[1] == 0xFFFFFFFFFFFFFFFFL); - - expect(result.f1.length == 2 - && result.f1[0] == 0x8000000000000000L - && result.f1[1] == 0x7FFFFFFFFFFFFFFFL); - } - - { - Tuple2 result = Test.listMinmaxFloat - (new float[] { - -Float.MAX_VALUE, - Float.MAX_VALUE, - Float.NEGATIVE_INFINITY, - Float.POSITIVE_INFINITY - }, - new double[] { - -Double.MAX_VALUE, - Double.MAX_VALUE, - Double.NEGATIVE_INFINITY, - Double.POSITIVE_INFINITY - }); - - expect(result.f0.length == 4 - && result.f0[0] == -Float.MAX_VALUE - && result.f0[1] == Float.MAX_VALUE - && result.f0[2] == Float.NEGATIVE_INFINITY - && result.f0[3] == Float.POSITIVE_INFINITY); - - expect(result.f1.length == 4 - && result.f1[0] == -Double.MAX_VALUE - && result.f1[1] == Double.MAX_VALUE - && result.f1[2] == Double.NEGATIVE_INFINITY - && result.f1[3] == Double.POSITIVE_INFINITY); - } - } - - public static void expect(boolean v) { - if (!v) { - throw new AssertionError(); - } - } -} diff --git a/tests/runtime/numbers/wit_exports_test_numbers_TestImpl.java b/tests/runtime/numbers/wit_exports_test_numbers_TestImpl.java deleted file mode 100644 index 2c36a8701..000000000 --- a/tests/runtime/numbers/wit_exports_test_numbers_TestImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -package wit.exports.test.numbers; - -public class TestImpl { - public static byte roundtripU8(byte a) { - return a; - } - - public static byte roundtripS8(byte a) { - return a; - } - - public static short roundtripU16(short a) { - return a; - } - - public static short roundtripS16(short a) { - return a; - } - - public static int roundtripU32(int a) { - return a; - } - - public static int roundtripS32(int a) { - return a; - } - - public static long roundtripU64(long a) { - return a; - } - - public static long roundtripS64(long a) { - return a; - } - - public static float roundtripF32(float a) { - return a; - } - - public static double roundtripF64(double a) { - return a; - } - - public static int roundtripChar(int a) { - return a; - } - - private static int scalar = 0; - - public static void setScalar(int a) { - scalar = a; - } - - public static int getScalar() { - return scalar; - } -} diff --git a/tests/runtime/numbers/wit_worlds_NumbersImpl.java b/tests/runtime/numbers/wit_worlds_NumbersImpl.java deleted file mode 100644 index 02c01d9e1..000000000 --- a/tests/runtime/numbers/wit_worlds_NumbersImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -package wit.worlds; - -import wit.imports.test.numbers.Test; - -public class NumbersImpl { - private static void expect(boolean v) { - if (!v) { - throw new AssertionError(); - } - } - - public static void testImports() { - expect(Test.roundtripU8((byte) 1) == (byte) 1); - expect(Test.roundtripU8((byte) 0) == (byte) 0); - expect(Test.roundtripU8((byte) 0xFF) == (byte) 0xFF); - - expect(Test.roundtripS8((byte) 1) == (byte) 1); - expect(Test.roundtripS8(Byte.MIN_VALUE) == Byte.MIN_VALUE); - expect(Test.roundtripS8(Byte.MAX_VALUE) == Byte.MAX_VALUE); - - expect(Test.roundtripU16((short) 1) == (short) 1); - expect(Test.roundtripU16((short) 0) == (short) 0); - expect(Test.roundtripU16((short) 0xFFFF) == (short) 0xFFFF); - - expect(Test.roundtripS16((short) 1) == (short) 1); - expect(Test.roundtripS16(Short.MIN_VALUE) == Short.MIN_VALUE); - expect(Test.roundtripS16(Short.MAX_VALUE) == Short.MAX_VALUE); - - expect(Test.roundtripU32(1) == 1); - expect(Test.roundtripU32(0) == 0); - expect(Test.roundtripU32(0xFFFFFFFF) == 0xFFFFFFFF); - - expect(Test.roundtripS32(1) == 1); - expect(Test.roundtripS32(Integer.MIN_VALUE) == Integer.MIN_VALUE); - expect(Test.roundtripS32(Integer.MAX_VALUE) == Integer.MAX_VALUE); - - expect(Test.roundtripU64(1L) == 1); - expect(Test.roundtripU64(0L) == 0L); - expect(Test.roundtripU64(0xFFFFFFFFFFFFFFFFL) == 0xFFFFFFFFFFFFFFFFL); - - expect(Test.roundtripS64(1L) == 1L); - expect(Test.roundtripS64(Long.MIN_VALUE) == Long.MIN_VALUE); - expect(Test.roundtripS64(Long.MAX_VALUE) == Long.MAX_VALUE); - - expect(Test.roundtripF32(1.0F) == 1.0F); - expect(Test.roundtripF32(Float.POSITIVE_INFINITY) == Float.POSITIVE_INFINITY); - expect(Test.roundtripF32(Float.NEGATIVE_INFINITY) == Float.NEGATIVE_INFINITY); - expect(Float.isNaN(Test.roundtripF32(Float.NaN))); - - expect(Test.roundtripF64(1.0) == 1.0); - expect(Test.roundtripF64(Double.POSITIVE_INFINITY) == Double.POSITIVE_INFINITY); - expect(Test.roundtripF64(Double.NEGATIVE_INFINITY) == Double.NEGATIVE_INFINITY); - expect(Double.isNaN(Test.roundtripF64(Double.NaN))); - - expect(Test.roundtripChar((int) 'a') == (int) 'a'); - expect(Test.roundtripChar((int) ' ') == (int) ' '); - expect(Test.roundtripChar("🚩".codePointAt(0)) == "🚩".codePointAt(0)); - - Test.setScalar(2); - expect(Test.getScalar() == 2); - Test.setScalar(4); - expect(Test.getScalar() == 4); - } - -} diff --git a/tests/runtime/records/wit_exports_test_records_TestImpl.java b/tests/runtime/records/wit_exports_test_records_TestImpl.java deleted file mode 100644 index ae26a849a..000000000 --- a/tests/runtime/records/wit_exports_test_records_TestImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package wit.exports.test.records; - -import wit.worlds.Records.Tuple1; -import wit.worlds.Records.Tuple2; -import wit.worlds.Records.Tuple3; - -public class TestImpl { - public static Tuple2 multipleResults() { - return new Tuple2<>((byte) 100, (short) 200); - } - - public static Tuple2 swapTuple(Tuple2 tuple) { - return new Tuple2<>(tuple.f1, tuple.f0); - } - - public static Test.F1 roundtripFlags1(Test.F1 a) { - return a; - } - - public static Test.F2 roundtripFlags2(Test.F2 a) { - return a; - } - - public static Tuple3 roundtripFlags3 - (Test.Flag8 a, Test.Flag16 b, Test.Flag32 c) - { - return new Tuple3<>(a, b, c); - } - - public static Test.R1 roundtripRecord1(Test.R1 a) { - return a; - } - - public static Tuple1 tuple1(Tuple1 a) { - return a; - } -} diff --git a/tests/runtime/records/wit_worlds_RecordsImpl.java b/tests/runtime/records/wit_worlds_RecordsImpl.java deleted file mode 100644 index e9b48f65f..000000000 --- a/tests/runtime/records/wit_worlds_RecordsImpl.java +++ /dev/null @@ -1,72 +0,0 @@ -package wit.worlds; - -import wit.worlds.Records.Tuple1; -import wit.worlds.Records.Tuple2; -import wit.worlds.Records.Tuple3; -import wit.imports.test.records.Test; - -public class RecordsImpl { - public static void testImports() { - { - Tuple2 results = Test.multipleResults(); - - expect(results.f0 == (byte) 4); - expect(results.f1 == (short) 5); - } - - { - Tuple2 results = Test.swapTuple(new Tuple2<>((byte) 1, 2)); - - expect(results.f0 == 2); - expect(results.f1 == (byte) 1); - } - - expect(Test.roundtripFlags1(Test.F1.A).value == Test.F1.A.value); - expect(Test.roundtripFlags1(new Test.F1((byte) 0)).value == (byte) 0); - expect(Test.roundtripFlags1(Test.F1.B).value == Test.F1.B.value); - expect(Test.roundtripFlags1(new Test.F1((byte) (Test.F1.A.value | Test.F1.B.value))).value - == (byte) (Test.F1.A.value | Test.F1.B.value)); - - expect(Test.roundtripFlags2(Test.F2.C).value == Test.F2.C.value); - expect(Test.roundtripFlags2(new Test.F2((byte) 0)).value == (byte) 0); - expect(Test.roundtripFlags2(Test.F2.D).value == Test.F2.D.value); - expect(Test.roundtripFlags2(new Test.F2((byte) (Test.F2.C.value | Test.F2.E.value))).value - == (byte) (Test.F2.C.value | Test.F2.E.value)); - - { - Tuple3 results = - Test.roundtripFlags3(Test.Flag8.B0, Test.Flag16.B1, Test.Flag32.B2); - - expect(results.f0.value == Test.Flag8.B0.value); - expect(results.f1.value == Test.Flag16.B1.value); - expect(results.f2.value == Test.Flag32.B2.value); - } - - { - Test.R1 result = Test.roundtripRecord1(new Test.R1((byte) 8, Test.F1.A)); - - expect(result.a == (byte) 8); - expect(result.b.value == Test.F1.A.value); - } - - { - Test.R1 result = Test.roundtripRecord1 - (new Test.R1((byte) 0, new Test.F1((byte) (Test.F1.A.value | Test.F1.B.value)))); - - expect(result.a == (byte) 0); - expect(result.b.value == (byte) (Test.F1.A.value | Test.F1.B.value)); - } - - { - Tuple1 result = Test.tuple1(new Tuple1<>((byte) 1)); - - expect(result.f0 == 1); - } - } - - private static void expect(boolean v) { - if (!v) { - throw new AssertionError(); - } - } -} diff --git a/tests/runtime/smoke/wit_worlds_SmokeImpl.java b/tests/runtime/smoke/wit_worlds_SmokeImpl.java deleted file mode 100644 index 54bac0840..000000000 --- a/tests/runtime/smoke/wit_worlds_SmokeImpl.java +++ /dev/null @@ -1,7 +0,0 @@ -package wit.worlds; - -public class SmokeImpl { - public static void thunk() { - wit.imports.test.smoke.Imports.thunk(); - } -} diff --git a/tests/runtime/variants/wit_exports_test_variants_TestImpl.java b/tests/runtime/variants/wit_exports_test_variants_TestImpl.java deleted file mode 100644 index 0629e095c..000000000 --- a/tests/runtime/variants/wit_exports_test_variants_TestImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package wit.exports.test.variants; - -import wit.worlds.Variants.Result; -import wit.worlds.Variants.Tuple0; -import wit.worlds.Variants.Tuple3; -import wit.worlds.Variants.Tuple4; -import wit.worlds.Variants.Tuple6; - -public class TestImpl { - public static Byte roundtripOption(Float a) { - return a == null ? null : (byte) (float) a; - } - - public static Result roundtripResult(Result a) { - switch (a.tag) { - case Result.OK: return Result.ok((double) a.getOk()); - case Result.ERR: return Result.err((byte) (float) a.getErr()); - default: throw new AssertionError(); - } - } - - public static Test.E1 roundtripEnum(Test.E1 a) { - return a; - } - - public static boolean invertBool(boolean a) { - return !a; - } - - public static Tuple6 - variantCasts(Tuple6 a) - { - return a; - } - - public static Tuple4 - variantZeros(Tuple4 a) - { - return a; - } - - public static void variantTypedefs(Integer a, boolean b, Result c) { } - - public static Tuple3, Test.MyErrno> - variantEnums(boolean a, Result b, Test.MyErrno c) - { - return new Tuple3<>(a, b, c); - } -} diff --git a/tests/runtime/variants/wit_worlds_VariantsImpl.java b/tests/runtime/variants/wit_worlds_VariantsImpl.java deleted file mode 100644 index fbbf1c9e6..000000000 --- a/tests/runtime/variants/wit_worlds_VariantsImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package wit.worlds; - -import wit.worlds.Variants.Result; -import wit.worlds.Variants.Tuple0; -import wit.worlds.Variants.Tuple3; -import wit.worlds.Variants.Tuple4; -import wit.worlds.Variants.Tuple6; -import wit.imports.test.variants.Test; - -public class VariantsImpl { - public static void testImports() { - expect(Test.roundtripOption(1.0F) == (byte) 1); - expect(Test.roundtripOption(null) == null); - expect(Test.roundtripOption(2.0F) == (byte) 2); - - { - Result result = Test.roundtripResult(Result.ok(2)); - expect(result.tag == Result.OK && result.getOk() == 2.0D); - } - - { - Result result = Test.roundtripResult(Result.ok(4)); - expect(result.tag == Result.OK && result.getOk() == 4.0D); - } - - { - Result result = Test.roundtripResult(Result.err(5.3F)); - expect(result.tag == Result.ERR && result.getErr() == (byte) 5); - } - - - expect(Test.roundtripEnum(Test.E1.A) == Test.E1.A); - expect(Test.roundtripEnum(Test.E1.B) == Test.E1.B); - - expect(Test.invertBool(true) == false); - expect(Test.invertBool(false) == true); - - { - Tuple6 result - = Test.variantCasts(new Tuple6<>(Test.C1.a(1), - Test.C2.a(2), - Test.C3.a(3), - Test.C4.a(4L), - Test.C5.a(5L), - Test.C6.a(6.0F))); - - expect(result.f0.tag == Test.C1.A && result.f0.getA() == 1); - expect(result.f1.tag == Test.C2.A && result.f1.getA() == 2); - expect(result.f2.tag == Test.C3.A && result.f2.getA() == 3); - expect(result.f3.tag == Test.C4.A && result.f3.getA() == 4L); - expect(result.f4.tag == Test.C5.A && result.f4.getA() == 5L); - expect(result.f5.tag == Test.C6.A && result.f5.getA() == 6.0F); - } - - { - Tuple6 result - = Test.variantCasts(new Tuple6<>(Test.C1.b(1L), - Test.C2.b(2.0F), - Test.C3.b(3.0D), - Test.C4.b(4.0F), - Test.C5.b(5.0D), - Test.C6.b(6.0D))); - - expect(result.f0.tag == Test.C1.B && result.f0.getB() == 1L); - expect(result.f1.tag == Test.C2.B && result.f1.getB() == 2.0F); - expect(result.f2.tag == Test.C3.B && result.f2.getB() == 3.0D); - expect(result.f3.tag == Test.C4.B && result.f3.getB() == 4.0F); - expect(result.f4.tag == Test.C5.B && result.f4.getB() == 5.0D); - expect(result.f5.tag == Test.C6.B && result.f5.getB() == 6.0D); - } - - { - Tuple4 result - = Test.variantZeros(new Tuple4<>(Test.Z1.a(1), - Test.Z2.a(2L), - Test.Z3.a(3.0F), - Test.Z4.a(4.0D))); - - expect(result.f0.tag == Test.Z1.A && result.f0.getA() == 1); - expect(result.f1.tag == Test.Z2.A && result.f1.getA() == 2L); - expect(result.f2.tag == Test.Z3.A && result.f2.getA() == 3.0F); - expect(result.f3.tag == Test.Z4.A && result.f3.getA() == 4.0D); - } - - { - Tuple4 result - = Test.variantZeros(new Tuple4<>(Test.Z1.b(), - Test.Z2.b(), - Test.Z3.b(), - Test.Z4.b())); - - expect(result.f0.tag == Test.Z1.B); - expect(result.f1.tag == Test.Z2.B); - expect(result.f2.tag == Test.Z3.B); - expect(result.f3.tag == Test.Z4.B); - } - - Test.variantTypedefs(null, false, Result.err(Tuple0.INSTANCE)); - - { - Tuple3, Test.MyErrno> result - = Test.variantEnums(true, Result.ok(Tuple0.INSTANCE), Test.MyErrno.SUCCESS); - - expect(result.f0 == false); - expect(result.f1.tag == Result.ERR); - expect(result.f2 == Test.MyErrno.A); - } - } - - private static void expect(boolean v) { - if (!v) { - throw new AssertionError(); - } - } -} From a7799820309229c1bc5df9f86c8c7af312137a2f Mon Sep 17 00:00:00 2001 From: Jiaxiao Zhou Date: Wed, 12 Mar 2025 12:45:33 -0700 Subject: [PATCH 10/14] Go: Remove Go from wit-bindgen (#1195) This commit removes the Go language generator from the wit-bindgen project. The Go bindings have been moved to a separate repository, and all Go-related code and tests have been deleted. This commit updates CLI to redirect users to the new Go bindings repository. The Go bindings can now be found at: https://github.com/bytecodealliance/go-modules Signed-off-by: Jiaxiao Zhou --- .github/workflows/main.yml | 11 +- Cargo.lock | 27 - Cargo.toml | 4 +- ci/publish.rs | 1 - crates/go/Cargo.toml | 26 - crates/go/src/bindgen.rs | 691 ---------- crates/go/src/imports.rs | 172 --- crates/go/src/interface.rs | 1224 ----------------- crates/go/src/lib.rs | 407 ------ crates/go/tests/codegen.rs | 98 -- src/bin/wit-bindgen.rs | 8 +- tests/README.md | 7 +- tests/runtime/flavorful/wasm.go | 156 --- tests/runtime/lists/wasm.go | 269 ---- tests/runtime/main.rs | 89 +- tests/runtime/many_arguments/wasm.go | 39 - tests/runtime/numbers/wasm.go | 202 --- tests/runtime/records/wasm.go | 94 -- tests/runtime/resource_borrow_export/wasm.go | 25 - tests/runtime/resource_borrow_import/wasm.go | 21 - tests/runtime/resource_borrow_simple/wasm.go | 20 - .../resource_import_and_export/wasm.go | 45 - tests/runtime/resources/wasm.go | 100 -- tests/runtime/serverless/wasm.go | 33 - tests/runtime/smoke/wasm.go | 18 - 25 files changed, 15 insertions(+), 3772 deletions(-) delete mode 100644 crates/go/Cargo.toml delete mode 100644 crates/go/src/bindgen.rs delete mode 100644 crates/go/src/imports.rs delete mode 100644 crates/go/src/interface.rs delete mode 100644 crates/go/src/lib.rs delete mode 100644 crates/go/tests/codegen.rs delete mode 100644 tests/runtime/flavorful/wasm.go delete mode 100644 tests/runtime/lists/wasm.go delete mode 100644 tests/runtime/many_arguments/wasm.go delete mode 100644 tests/runtime/numbers/wasm.go delete mode 100644 tests/runtime/records/wasm.go delete mode 100644 tests/runtime/resource_borrow_export/wasm.go delete mode 100644 tests/runtime/resource_borrow_import/wasm.go delete mode 100644 tests/runtime/resource_borrow_simple/wasm.go delete mode 100644 tests/runtime/resource_import_and_export/wasm.go delete mode 100644 tests/runtime/resources/wasm.go delete mode 100644 tests/runtime/serverless/wasm.go delete mode 100644 tests/runtime/smoke/wasm.go diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2dfbe76a3..de6f0bb49 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] # moonbit removed from language matrix for now - causing CI failures - lang: [c, rust, go, csharp] + lang: [c, rust, csharp] exclude: # For now csharp doesn't work on macos, so exclude it from testing. - os: macos-latest @@ -99,15 +99,6 @@ jobs: shell: powershell if: matrix.os == 'windows-latest' && matrix.lang == 'moonbit' - - uses: actions/setup-go@v4 - if: matrix.lang == 'go' - with: - go-version: '1.20' - - uses: acifani/setup-tinygo@v2 - if: matrix.lang == 'go' - with: - tinygo-version: 0.31.0 - - run: | cargo test \ -p wit-bindgen-cli \ diff --git a/Cargo.lock b/Cargo.lock index bdc68d6eb..8c1e39301 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2587,11 +2587,9 @@ dependencies = [ "wit-bindgen-c", "wit-bindgen-core", "wit-bindgen-csharp", - "wit-bindgen-go", "wit-bindgen-markdown", "wit-bindgen-moonbit", "wit-bindgen-rust", - "wit-bindgen-teavm-java", "wit-component", "wit-parser 0.227.0", ] @@ -2622,18 +2620,6 @@ dependencies = [ "wit-parser 0.227.0", ] -[[package]] -name = "wit-bindgen-go" -version = "0.40.0" -dependencies = [ - "anyhow", - "clap", - "heck 0.5.0", - "test-helpers", - "wit-bindgen-c", - "wit-bindgen-core", -] - [[package]] name = "wit-bindgen-markdown" version = "0.40.0" @@ -2699,19 +2685,6 @@ dependencies = [ "wit-bindgen-rust", ] -[[package]] -name = "wit-bindgen-teavm-java" -version = "0.40.0" -dependencies = [ - "anyhow", - "clap", - "heck 0.5.0", - "test-helpers", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - [[package]] name = "wit-component" version = "0.227.0" diff --git a/Cargo.toml b/Cargo.toml index 372cc52d8..6764cc4a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ wit-component = "0.227.0" wit-bindgen-core = { path = 'crates/core', version = '0.40.0' } wit-bindgen-c = { path = 'crates/c', version = '0.40.0' } wit-bindgen-rust = { path = "crates/rust", version = "0.40.0" } -wit-bindgen-go = { path = 'crates/go', version = '0.40.0' } wit-bindgen-csharp = { path = 'crates/csharp', version = '0.40.0' } wit-bindgen-markdown = { path = 'crates/markdown', version = '0.40.0' } wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.40.0' } @@ -59,7 +58,6 @@ wit-bindgen-rust = { workspace = true, features = ['clap'], optional = true } wit-bindgen-c = { workspace = true, features = ['clap'], optional = true } wit-bindgen-markdown = { workspace = true, features = ['clap'], optional = true } wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true } -wit-bindgen-go = { workspace = true, features = ['clap'], optional = true } wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } wit-component = { workspace = true } wasm-encoder = { workspace = true } @@ -77,7 +75,7 @@ default = [ c = ['dep:wit-bindgen-c'] rust = ['dep:wit-bindgen-rust'] markdown = ['dep:wit-bindgen-markdown'] -go = ['dep:wit-bindgen-go'] +go = [] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] diff --git a/ci/publish.rs b/ci/publish.rs index ed18210db..1b8566256 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -20,7 +20,6 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wit-bindgen-core", "wit-bindgen-c", "wit-bindgen-rust", - "wit-bindgen-go", "wit-bindgen-csharp", "wit-bindgen-markdown", "wit-bindgen-moonbit", diff --git a/crates/go/Cargo.toml b/crates/go/Cargo.toml deleted file mode 100644 index 88e1e80f3..000000000 --- a/crates/go/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "wit-bindgen-go" -authors = ["Mossaka "] -version = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -license = { workspace = true } -homepage = 'https://github.com/bytecodealliance/wit-bindgen' -description = """ -TinyGo/Go bindings generator for WIT and the component model, typically used -through the `wit-bindgen-cli` crate. -""" - -[lib] -test = false -doctest = false - -[dependencies] -wit-bindgen-core = { workspace = true } -anyhow = { workspace = true } -heck = { workspace = true } -clap = { workspace = true, optional = true } -wit-bindgen-c = { workspace = true } - -[dev-dependencies] -test-helpers = { path = '../test-helpers' } diff --git a/crates/go/src/bindgen.rs b/crates/go/src/bindgen.rs deleted file mode 100644 index 2398d7678..000000000 --- a/crates/go/src/bindgen.rs +++ /dev/null @@ -1,691 +0,0 @@ -use std::fmt::Write as _; - -use heck::{ToSnakeCase, ToUpperCamelCase}; -use wit_bindgen_c::{flags_repr, int_repr}; -use wit_bindgen_core::wit_parser::Handle::{Borrow, Own}; -use wit_bindgen_core::wit_parser::{Field, Function, Type, TypeDefKind}; -use wit_bindgen_core::{dealias, uwriteln, Direction, Source}; - -use super::avoid_keyword; -use crate::interface; - -pub(crate) struct FunctionBindgen<'a, 'b> { - pub(crate) interface: &'a mut interface::InterfaceGenerator<'b>, - pub(crate) func: &'a Function, - pub(crate) c_args: Vec, - pub(crate) args: Vec, - pub(crate) lower_src: Source, - pub(crate) lift_src: Source, -} - -impl<'a, 'b> FunctionBindgen<'a, 'b> { - pub(crate) fn new( - interface: &'a mut interface::InterfaceGenerator<'b>, - func: &'a Function, - ) -> Self { - Self { - interface, - func, - c_args: Vec::new(), - args: Vec::new(), - lower_src: Source::default(), - lift_src: Source::default(), - } - } - - pub(crate) fn process_args(&mut self) { - self.func - .params - .iter() - .for_each(|(name, ty)| match self.interface.direction { - Direction::Import => self.lower(&avoid_keyword(&name.to_snake_case()), ty), - Direction::Export => self.lift(&avoid_keyword(&name.to_snake_case()), ty), - }); - } - - pub(crate) fn process_returns(&mut self) { - if let Some(ty) = &self.func.result { - match self.interface.direction { - Direction::Import => self.lift("ret", ty), - Direction::Export => self.lower("result", ty), - } - } - } - - pub(crate) fn lower(&mut self, name: &str, ty: &Type) { - let lower_name = format!("lower_{name}"); - self.lower_value(name, ty, lower_name.as_ref()); - - // Check whether or not the C variable needs to be freed. - // If this variable is in export function, which will be returned to host to use. - // There is no need to free return variables. - // If this variable does not own anything, it does not need to be freed. - // If this variable is in inner node of the recursive call, no need to be freed. - // This is because the root node's call to free will recursively free the whole tree. - // Otherwise, free this variable. - // - // TODO: should test if free is necessary - if matches!(self.interface.direction, Direction::Import) && false { - self.lower_src - .push_str(&self.interface.free_c_arg(ty, &format!("&{lower_name}"))); - } - self.c_args.push(lower_name); - } - - pub(crate) fn lower_list_value(&mut self, param: &str, l: &Type, lower_name: &str) { - let list_ty = self.interface.gen.get_c_ty(l); - uwriteln!( - self.lower_src, - "if len({param}) == 0 {{ - {lower_name}.ptr = nil - {lower_name}.len = 0 - }} else {{ - var empty_{lower_name} {list_ty} - {lower_name}.ptr = (*{list_ty})(C.malloc(C.size_t(len({param})) * C.size_t(unsafe.Sizeof(empty_{lower_name})))) - {lower_name}.len = C.size_t(len({param}))" - ); - - uwriteln!(self.lower_src, "for {lower_name}_i := range {param} {{"); - uwriteln!(self.lower_src, - "{lower_name}_ptr := (*{list_ty})(unsafe.Pointer(uintptr(unsafe.Pointer({lower_name}.ptr)) + - uintptr({lower_name}_i)*unsafe.Sizeof(empty_{lower_name})))" - ); - - let param = &format!("{param}[{lower_name}_i]"); - let lower_name = &format!("{lower_name}_ptr"); - - if let Some(inner) = self.interface.extract_list_ty(l) { - self.lower_list_value(param, &inner.clone(), lower_name); - } else { - self.lower_value(param, l, &format!("{lower_name}_value")); - uwriteln!(self.lower_src, "*{lower_name} = {lower_name}_value"); - } - - uwriteln!(self.lower_src, "}}"); - uwriteln!(self.lower_src, "}}"); - } - - pub(crate) fn lower_result_value( - &mut self, - param: &str, - ty: &Type, - lower_name: &str, - lower_inner_name1: &str, - lower_inner_name2: &str, - ) { - // lower_inner_name could be {lower_name}.val if it's used in import - // else, it could be either ret or err - - let (ok, err) = self.interface.extract_result_ty(ty); - uwriteln!(self.lower_src, "if {param}.IsOk() {{"); - if let Some(ok_inner) = ok { - self.interface.gen.with_import_unsafe(true); - let c_target_name = self.interface.gen.get_c_ty(&ok_inner); - uwriteln!( - self.lower_src, - "{lower_name}_ptr := (*{c_target_name})(unsafe.Pointer({lower_inner_name1}))" - ); - self.lower_value( - &format!("{param}.Unwrap()"), - &ok_inner, - &format!("{lower_name}_val"), - ); - uwriteln!(self.lower_src, "*{lower_name}_ptr = {lower_name}_val"); - } - self.lower_src.push_str("} else {\n"); - if let Some(err_inner) = err { - self.interface.gen.with_import_unsafe(true); - let c_target_name = self.interface.gen.get_c_ty(&err_inner); - uwriteln!( - self.lower_src, - "{lower_name}_ptr := (*{c_target_name})(unsafe.Pointer({lower_inner_name2}))" - ); - self.lower_value( - &format!("{param}.UnwrapErr()"), - &err_inner, - &format!("{lower_name}_val"), - ); - uwriteln!(self.lower_src, "*{lower_name}_ptr = {lower_name}_val"); - } - self.lower_src.push_str("}\n"); - } - - /// Lower a value to a string representation. - /// - /// # Parameters - /// - /// * `param` - The string representation of the parameter of a function - /// * `ty` - A reference to a `Type` that specifies the type of the value. - /// * `lower_name` - A reference to a string that represents the name to be used for the lower value. - pub(crate) fn lower_value(&mut self, param: &str, ty: &Type, lower_name: &str) { - match ty { - Type::Bool => { - uwriteln!(self.lower_src, "{lower_name} := {param}",); - } - Type::String => { - self.interface.gen.with_import_unsafe(true); - uwriteln!( - self.lower_src, - "var {lower_name} {value}", - value = self.interface.gen.get_c_ty(ty), - ); - uwriteln!( - self.lower_src, - " - // use unsafe.Pointer to avoid copy - {lower_name}.ptr = (*uint8)(unsafe.Pointer(C.CString({param}))) - {lower_name}.len = C.size_t(len({param}))" - ); - } - Type::ErrorContext => todo!("impl error-context"), - Type::Id(id) => { - let ty = &self.interface.resolve.types[*id]; // receive type - - match &ty.kind { - TypeDefKind::Record(r) => { - let c_typedef_target = self.interface.gen.get_c_ty(&Type::Id(*id)); // okay to unwrap because a record must have a name - uwriteln!(self.lower_src, "var {lower_name} {c_typedef_target}"); - for field in r.fields.iter() { - let c_field_name = &self.get_c_field_name(field); - let field_name = &self.get_go_field_name(field); - - self.lower_value( - &format!("{param}.{field_name}"), - &field.ty, - &format!("{lower_name}_{c_field_name}"), - ); - uwriteln!( - self.lower_src, - "{lower_name}.{c_field_name} = {lower_name}_{c_field_name}" - ) - } - } - - TypeDefKind::Flags(f) => { - let int_repr = int_repr(flags_repr(f)); - uwriteln!(self.lower_src, "{lower_name} := C.{int_repr}({param})"); - } - TypeDefKind::Tuple(t) => { - let c_typedef_target = self.interface.gen.get_c_ty(&Type::Id(*id)); // okay to unwrap because a record must have a name - uwriteln!(self.lower_src, "var {lower_name} {c_typedef_target}"); - for (i, ty) in t.types.iter().enumerate() { - self.lower_value( - &format!("{param}.F{i}"), - ty, - &format!("{lower_name}_f{i}"), - ); - uwriteln!(self.lower_src, "{lower_name}.f{i} = {lower_name}_f{i}"); - } - } - TypeDefKind::Option(o) => { - let c_typedef_target = self.interface.gen.get_c_ty(&Type::Id(*id)); - uwriteln!(self.lower_src, "var {lower_name} {c_typedef_target}"); - uwriteln!(self.lower_src, "if {param}.IsSome() {{"); - self.lower_value( - &format!("{param}.Unwrap()"), - o, - &format!("{lower_name}_val"), - ); - uwriteln!(self.lower_src, "{lower_name}.val = {lower_name}_val"); - uwriteln!(self.lower_src, "{lower_name}.is_some = true"); - self.lower_src.push_str("}\n"); - } - TypeDefKind::Result(_) => { - let c_typedef_target = self.interface.gen.get_c_ty(&Type::Id(*id)); - - uwriteln!(self.lower_src, "var {lower_name} {c_typedef_target}"); - uwriteln!(self.lower_src, "{lower_name}.is_err = {param}.IsErr()"); - let inner_name = format!("&{lower_name}.val"); - self.lower_result_value( - param, - &Type::Id(*id), - lower_name, - &inner_name, - &inner_name, - ); - } - TypeDefKind::List(l) => { - self.interface.gen.with_import_unsafe(true); - let c_typedef_target = self.interface.gen.get_c_ty(&Type::Id(*id)); - - uwriteln!(self.lower_src, "var {lower_name} {c_typedef_target}"); - self.lower_list_value(param, l, lower_name); - } - TypeDefKind::Type(t) => { - uwriteln!( - self.lower_src, - "var {lower_name} {value}", - value = self.interface.gen.get_c_ty(t), - ); - self.lower_value(param, t, &format!("{lower_name}_val")); - uwriteln!(self.lower_src, "{lower_name} = {lower_name}_val"); - } - TypeDefKind::Variant(v) => { - self.interface.gen.with_import_unsafe(true); - - let c_typedef_target = self.interface.gen.get_c_ty(&Type::Id(*id)); - let ty = self.interface.get_ty(&Type::Id(*id)); - uwriteln!(self.lower_src, "var {lower_name} {c_typedef_target}"); - for (i, case) in v.cases.iter().enumerate() { - let case_name = case.name.to_upper_camel_case(); - uwriteln!( - self.lower_src, - "if {param}.Kind() == {ty}Kind{case_name} {{" - ); - if let Some(ty) = case.ty.as_ref() { - let name = self.interface.gen.get_c_ty(ty); - uwriteln!( - self.lower_src, - " - {lower_name}.tag = {i} - {lower_name}_ptr := (*{name})(unsafe.Pointer(&{lower_name}.val))" - ); - self.lower_value( - &format!("{param}.Get{case_name}()"), - ty, - &format!("{lower_name}_val"), - ); - uwriteln!(self.lower_src, "*{lower_name}_ptr = {lower_name}_val"); - } else { - uwriteln!(self.lower_src, "{lower_name}.tag = {i}"); - } - self.lower_src.push_str("}\n"); - } - } - TypeDefKind::Enum(e) => { - let c_typedef_target = self.interface.gen.get_c_ty(&Type::Id(*id)); - let ty = self.interface.get_ty(&Type::Id(*id)); - uwriteln!(self.lower_src, "var {lower_name} {c_typedef_target}"); - for (i, case) in e.cases.iter().enumerate() { - let case_name = case.name.to_upper_camel_case(); - uwriteln!( - self.lower_src, - "if {param}.Kind() == {ty}Kind{case_name} {{" - ); - uwriteln!(self.lower_src, "{lower_name} = {i}"); - self.lower_src.push_str("}\n"); - } - } - TypeDefKind::Future(_) => todo!("impl future"), - TypeDefKind::Stream(_) => todo!("impl stream"), - TypeDefKind::Resource => todo!("impl resource"), - TypeDefKind::Handle(h) => { - match self.interface.direction { - Direction::Import => { - let c_typedef_target = self.interface.gen.get_c_ty(&Type::Id(*id)); - uwriteln!( - self.lower_src, - "var {lower_name} {c_typedef_target} - {lower_name}.__handle = C.int32_t({param})" - ) - } - Direction::Export => { - let resource = dealias( - self.interface.resolve, - *match h { - Borrow(resource) => resource, - Own(resource) => resource, - }, - ); - let ns = self.interface.c_namespace_of_resource(resource); - let snake = self.interface.resolve.types[resource] - .name - .as_ref() - .unwrap() - .to_snake_case(); - // If the resource is exported, then `type_resource` have created a - // internal bookkeeping map for this resource. We will need to - // use the map to get the resource interface. Otherwise, this resource - // is imported, and we will use the generated i32 handle directly. - if self.interface.gen.exported_resources.contains(&resource) { - let resource = dealias(self.interface.resolve, resource); - let c_typedef_target = - self.interface.gen.get_c_ty(&Type::Id(resource)); - let ty_name = self.interface.gen.type_names[&resource].clone(); - let private_type_name = ty_name.to_snake_case(); - self.interface.gen.with_import_unsafe(true); - uwriteln!(self.lower_src, - "{private_type_name}_mu.Lock() - {private_type_name}_next_id += 1 - {private_type_name}_pointers[{private_type_name}_next_id] = {param} - {private_type_name}_mu.Unlock() - {lower_name}_c := (*{c_typedef_target})(unsafe.Pointer(C.malloc(C.size_t(unsafe.Sizeof({c_typedef_target}{{}}))))) - {lower_name}_c.__handle = C.int32_t({private_type_name}_next_id) - {lower_name} := C.{ns}_{snake}_new({lower_name}_c) // pass the pointer directly - set{ty_name}OwningHandler({param}, int32({lower_name}.__handle))" - ); - } else { - // need to construct either an own or borrowed C handle type. - - let c_typedef_target = match h { - Own(_) => { - let mut own = ns.clone(); - own.push_str("_own_"); - own.push_str(&snake); - own.push_str("_t"); - own - } - Borrow(_) => { - let mut borrow = ns.clone(); - borrow.push_str("_borrow_"); - borrow.push_str(&snake); - borrow.push_str("_t"); - borrow - } - }; - uwriteln!( - self.lower_src, - "var {lower_name} C.{c_typedef_target} - {lower_name}.__handle = C.int32_t({param})" - ); - } - } - } - } - TypeDefKind::Unknown => unreachable!(), - } - } - a => { - uwriteln!( - self.lower_src, - "{lower_name} := {c_type_name}({param_name})", - c_type_name = self.interface.gen.get_c_ty(a), - param_name = param, - ); - } - } - } - - pub(crate) fn lift(&mut self, name: &str, ty: &Type) { - let lift_name = format!("lift_{name}"); - self.lift_value(name, ty, lift_name.as_str()); - self.args.push(lift_name); - } - - pub(crate) fn lift_value(&mut self, param: &str, ty: &Type, lift_name: &str) { - match ty { - Type::Bool => { - uwriteln!(self.lift_src, "{lift_name} := {param}"); - } - Type::String => { - self.interface.gen.with_import_unsafe(true); - uwriteln!( - self.lift_src, - "var {name} {value} - {lift_name} = C.GoStringN((*C.char)(unsafe.Pointer({param}.ptr)), C.int({param}.len))", - name = lift_name, - value = self.interface.get_ty(ty), - ); - } - Type::ErrorContext => todo!("impl error-context"), - Type::Id(id) => { - let ty = &self.interface.resolve.types[*id]; // receive type - match &ty.kind { - TypeDefKind::Record(r) => { - uwriteln!( - self.lift_src, - "var {name} {ty_name}", - name = lift_name, - ty_name = self.interface.get_ty(&Type::Id(*id)), - ); - for field in r.fields.iter() { - let field_name = &self.get_go_field_name(field); - let c_field_name = &self.get_c_field_name(field); - self.lift_value( - &format!("{param}.{c_field_name}"), - &field.ty, - &format!("{lift_name}_{field_name}"), - ); - uwriteln!( - self.lift_src, - "{lift_name}.{field_name} = {lift_name}_{field_name}" - ); - } - } - TypeDefKind::Flags(_f) => { - let field = self.interface.get_ty(&Type::Id(*id)); - uwriteln!( - self.lift_src, - "var {name} {ty_name} - {lift_name} = {field}({param})", - name = lift_name, - ty_name = self.interface.get_ty(&Type::Id(*id)), - ); - } - TypeDefKind::Tuple(t) => { - uwriteln!( - self.lift_src, - "var {name} {ty_name}", - name = lift_name, - ty_name = self.interface.get_ty(&Type::Id(*id)), - ); - for (i, t) in t.types.iter().enumerate() { - self.lift_value( - &format!("{param}.f{i}"), - t, - &format!("{lift_name}_F{i}"), - ); - uwriteln!(self.lift_src, "{lift_name}.F{i} = {lift_name}_F{i}"); - } - } - TypeDefKind::Option(o) => { - let ty_name = self.interface.get_ty(&Type::Id(*id)); - uwriteln!(self.lift_src, "var {lift_name} {ty_name}"); - uwriteln!(self.lift_src, "if {param}.is_some {{"); - self.lift_value(&format!("{param}.val"), o, &format!("{lift_name}_val")); - - uwriteln!(self.lift_src, "{lift_name}.Set({lift_name}_val)"); - self.lift_src.push_str("} else {\n"); - uwriteln!(self.lift_src, "{lift_name}.Unset()"); - self.lift_src.push_str("}\n"); - } - TypeDefKind::Result(_) => { - self.interface.gen.with_result_option(true); - let ty_name = self.interface.get_ty(&Type::Id(*id)); - uwriteln!(self.lift_src, "var {lift_name} {ty_name}"); - let (ok, err) = self.interface.extract_result_ty(&Type::Id(*id)); - - // normal result route - uwriteln!(self.lift_src, "if {param}.is_err {{"); - if let Some(err_inner) = err { - let err_inner_name = self.interface.gen.get_c_ty(&err_inner); - self.interface.gen.with_import_unsafe(true); - uwriteln!(self.lift_src, "{lift_name}_ptr := *(*{err_inner_name})(unsafe.Pointer(&{param}.val))"); - self.lift_value( - &format!("{lift_name}_ptr"), - &err_inner, - &format!("{lift_name}_val"), - ); - uwriteln!(self.lift_src, "{lift_name}.SetErr({lift_name}_val)") - } else { - uwriteln!(self.lift_src, "{lift_name}.SetErr(struct{{}}{{}})") - } - uwriteln!(self.lift_src, "}} else {{"); - if let Some(ok_inner) = ok { - let ok_inner_name = self.interface.gen.get_c_ty(&ok_inner); - self.interface.gen.with_import_unsafe(true); - uwriteln!(self.lift_src, "{lift_name}_ptr := *(*{ok_inner_name})(unsafe.Pointer(&{param}.val))"); - self.lift_value( - &format!("{lift_name}_ptr"), - &ok_inner, - &format!("{lift_name}_val"), - ); - uwriteln!(self.lift_src, "{lift_name}.Set({lift_name}_val)") - } - uwriteln!(self.lift_src, "}}"); - } - TypeDefKind::List(l) => { - self.interface.gen.with_import_unsafe(true); - let list_ty = self.interface.get_ty(&Type::Id(*id)); - let c_ty_name = self.interface.gen.get_c_ty(l); - uwriteln!(self.lift_src, "var {lift_name} {list_ty}",); - uwriteln!(self.lift_src, "{lift_name} = make({list_ty}, {param}.len)"); - uwriteln!(self.lift_src, "if {param}.len > 0 {{"); - uwriteln!(self.lift_src, "for {lift_name}_i := 0; {lift_name}_i < int({param}.len); {lift_name}_i++ {{"); - uwriteln!(self.lift_src, "var empty_{lift_name} {c_ty_name}"); - uwriteln!( - self.lift_src, - "{lift_name}_ptr := *(*{c_ty_name})(unsafe.Pointer(uintptr(unsafe.Pointer({param}.ptr)) + - uintptr({lift_name}_i)*unsafe.Sizeof(empty_{lift_name})))" - ); - - // If l is an empty tuple, set _ = {lift_name}_ptr - // this is a special case needs to be handled - if self.interface.is_empty_tuple_ty(l) { - uwriteln!(self.lift_src, "_ = {lift_name}_ptr"); - } - - self.lift_value( - &format!("{lift_name}_ptr"), - l, - &format!("list_{lift_name}"), - ); - - uwriteln!( - self.lift_src, - "{lift_name}[{lift_name}_i] = list_{lift_name}" - ); - self.lift_src.push_str("}\n"); - self.lift_src.push_str("}\n"); - // TODO: don't forget to free `ret` - } - TypeDefKind::Type(t) => { - uwriteln!( - self.lift_src, - "var {lift_name} {ty_name}", - ty_name = self.interface.get_ty(&Type::Id(*id)), - ); - self.lift_value(param, t, &format!("{lift_name}_val")); - uwriteln!(self.lift_src, "{lift_name} = {lift_name}_val"); - } - TypeDefKind::Variant(v) => { - self.interface.gen.with_import_unsafe(true); - let ty_name: String = self.interface.get_ty(&Type::Id(*id)); - uwriteln!(self.lift_src, "var {lift_name} {ty_name}"); - for (i, case) in v.cases.iter().enumerate() { - let case_name = case.name.to_upper_camel_case(); - self.lift_src - .push_str(&format!("if {param}.tag == {i} {{\n")); - if let Some(ty) = case.ty.as_ref() { - let c_ty_name = self.interface.gen.get_c_ty(ty); - uwriteln!( - self.lift_src, - "{lift_name}_ptr := *(*{c_ty_name})(unsafe.Pointer(&{param}.val))" - ); - self.lift_value( - &format!("{lift_name}_ptr"), - ty, - &format!("{lift_name}_val"), - ); - uwriteln!( - self.lift_src, - "{lift_name} = {ty_name}{case_name}({lift_name}_val)" - ) - } else { - uwriteln!(self.lift_src, "{lift_name} = {ty_name}{case_name}()"); - } - self.lift_src.push_str("}\n"); - } - } - TypeDefKind::Enum(e) => { - let ty_name = self.interface.get_ty(&Type::Id(*id)); - uwriteln!(self.lift_src, "var {lift_name} {ty_name}"); - for (i, case) in e.cases.iter().enumerate() { - let case_name = case.name.to_upper_camel_case(); - uwriteln!(self.lift_src, "if {param} == {i} {{"); - uwriteln!(self.lift_src, "{lift_name} = {ty_name}{case_name}()"); - self.lift_src.push_str("}\n"); - } - } - TypeDefKind::Future(_) => todo!("impl future"), - TypeDefKind::Stream(_) => todo!("impl stream"), - TypeDefKind::Resource => todo!("impl resource"), - TypeDefKind::Handle(h) => { - match self.interface.direction { - Direction::Import => { - let ty_name = self.interface.get_ty(&Type::Id(*id)); - uwriteln!( - self.lift_src, - "var {lift_name} {ty_name} - {lift_name} = {ty_name}({param}.__handle) - " - ); - } - Direction::Export => { - // If the resource is exported, then `type_resource` have created a - // internal bookkeeping map for this resource. We will need to - // use the map to get the resource interface. Otherwise, this resource - // is imported, and we will use the generated i32 handle directly. - let resource = dealias( - self.interface.resolve, - *match h { - Borrow(resource) => resource, - Own(resource) => resource, - }, - ); - // we want to get the namespace of the dealias resource since - // only the `rep` and `new` functions are generated in the namespace - // see issue: https://github.com/bytecodealliance/wit-bindgen/issues/763 - let ns = self.interface.c_namespace_of_resource(resource); - let snake = self.interface.resolve.types[resource] - .name - .as_ref() - .unwrap() - .to_snake_case(); - if self.interface.gen.exported_resources.contains(&resource) { - let resource_name: String = - self.interface.get_ty(&Type::Id(resource)).to_snake_case(); - match h { - Own(_) => { - uwriteln!( - self.lift_src, - "{lift_name}_rep := C.{ns}_{snake}_rep({param}) - {lift_name}_handle := int32({lift_name}_rep.__handle)" - ); - } - Borrow(_) => { - uwriteln!( - self.lift_src, - "{lift_name}_handle := int32({param}.__handle)" - ); - } - } - uwriteln!( - self.lift_src, - "{lift_name}, ok := {resource_name}_pointers[{lift_name}_handle] - if !ok {{ - panic(\"internal error: invalid handle\") - }}" - ); - } else { - let resource_name = self.interface.get_ty(&Type::Id(resource)); - uwriteln!( - self.lift_src, - "{lift_name} := {resource_name}({param}.__handle)", - ); - } - } - } - } - TypeDefKind::Unknown => unreachable!(), - } - } - a => { - let target_name = self.interface.get_ty(a); - - uwriteln!(self.lift_src, "var {lift_name} {target_name}",); - uwriteln!(self.lift_src, "{lift_name} = {target_name}({param})",); - } - } - } - - pub(crate) fn get_c_field_name(&mut self, field: &Field) -> String { - let name = wit_bindgen_c::to_c_ident(field.name.to_snake_case().as_str()); - avoid_keyword(&name) - } - - pub(crate) fn get_go_field_name(&mut self, field: &Field) -> String { - let name = &self.interface.field_name(field); - avoid_keyword(name) - } -} diff --git a/crates/go/src/imports.rs b/crates/go/src/imports.rs deleted file mode 100644 index ee6ccd66d..000000000 --- a/crates/go/src/imports.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::fmt::Write as _; - -use wit_bindgen_core::{uwriteln, Files, Source}; - -#[derive(Default)] -pub(crate) struct ImportRequirements { - // whether the generated code needs to import result and option - pub(crate) needs_result_option: bool, - - // whether the generated code needs to import "unsafe" - pub(crate) needs_import_unsafe: bool, - - // whether the generated code needs to import "fmt" - pub(crate) needs_fmt_import: bool, - - // whether the generated code needs to import "sync" - pub(crate) needs_sync_import: bool, - - pub(crate) src: Source, -} - -impl ImportRequirements { - pub(crate) fn generate(&mut self, snake: String, files: &mut Files, file_name: String) { - if self.needs_import_unsafe { - self.src.push_str("import \"unsafe\"\n"); - } - if self.needs_fmt_import { - self.src.push_str("import \"fmt\"\n"); - } - if self.needs_sync_import { - self.src.push_str("import \"sync\"\n\n"); - } - - if self.needs_result_option { - let mut result_option_src = Source::default(); - uwriteln!( - result_option_src, - "package {snake} - - // inspired from https://github.com/moznion/go-optional - - type optionKind int - - const ( - none optionKind = iota - some - ) - - type Option[T any] struct {{ - kind optionKind - val T - }} - - // IsNone returns true if the option is None. - func (o Option[T]) IsNone() bool {{ - return o.kind == none - }} - - // IsSome returns true if the option is Some. - func (o Option[T]) IsSome() bool {{ - return o.kind == some - }} - - // Unwrap returns the value if the option is Some. - func (o Option[T]) Unwrap() T {{ - if o.kind != some {{ - panic(\"Option is None\") - }} - return o.val - }} - - // Set sets the value and returns it. - func (o *Option[T]) Set(val T) T {{ - o.kind = some - o.val = val - return val - }} - - // Unset sets the value to None. - func (o *Option[T]) Unset() {{ - o.kind = none - }} - - // Some is a constructor for Option[T] which represents Some. - func Some[T any](v T) Option[T] {{ - return Option[T]{{ - kind: some, - val: v, - }} - }} - - // None is a constructor for Option[T] which represents None. - func None[T any]() Option[T] {{ - return Option[T]{{ - kind: none, - }} - }} - - type ResultKind int - - const ( - resultOk ResultKind = iota - resultErr - ) - - type Result[T any, E any] struct {{ - kind ResultKind - resultOk T - resultErr E - }} - - // IsOk returns true if the result is Ok. - func (r Result[T, E]) IsOk() bool {{ - return r.kind == resultOk - }} - - // IsErr returns true if the result is Err. - func (r Result[T, E]) IsErr() bool {{ - return r.kind == resultErr - }} - - // Unwrap returns the value if the result is Ok. - func (r Result[T, E]) Unwrap() T {{ - if r.kind != resultOk {{ - panic(\"Result is Err\") - }} - return r.resultOk - }} - - // UnwrapErr returns the value if the result is Err. - func (r Result[T, E]) UnwrapErr() E {{ - if r.kind != resultErr {{ - panic(\"Result is Ok\") - }} - return r.resultErr - }} - - // Set sets the value and returns it. - func (r *Result[T, E]) Set(val T) T {{ - r.kind = resultOk - r.resultOk = val - return val - }} - - // SetErr sets the value and returns it. - func (r *Result[T, E]) SetErr(val E) E {{ - r.kind = resultErr - r.resultErr = val - return val - }} - - // Ok is a constructor for Result[T, E] which represents Ok. - func Ok[T any, E any](v T) Result[T, E] {{ - return Result[T, E]{{ - kind: resultOk, - resultOk: v, - }} - }} - - // Err is a constructor for Result[T, E] which represents Err. - func Err[T any, E any](v E) Result[T, E] {{ - return Result[T, E]{{ - kind: resultErr, - resultErr: v, - }} - }} - " - ); - files.push(&file_name, result_option_src.as_bytes()); - } - } -} diff --git a/crates/go/src/interface.rs b/crates/go/src/interface.rs deleted file mode 100644 index 2dcedf184..000000000 --- a/crates/go/src/interface.rs +++ /dev/null @@ -1,1224 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::fmt::Write; - -use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase}; -use wit_bindgen_c::{ - c_func_name, gen_type_name, is_arg_by_pointer, owner_namespace as c_owner_namespace, - CTypeNameInfo, -}; -use wit_bindgen_core::wit_parser::{ - Docs, Enum, Field, Flags, Function, FunctionKind, Handle, InterfaceId, LiveTypes, Record, - Resolve, Result_, Tuple, Type, TypeDefKind, TypeId, TypeOwner, Variant, WorldKey, -}; -use wit_bindgen_core::{uwriteln, Direction, InterfaceGenerator as _, Source}; - -use super::{avoid_keyword, bindgen, TinyGo}; - -pub(crate) struct InterfaceGenerator<'a> { - pub(crate) src: Source, - pub(crate) preamble: Source, - pub(crate) gen: &'a mut TinyGo, - pub(crate) resolve: &'a Resolve, - pub(crate) interface: Option<(InterfaceId, &'a WorldKey)>, - pub(crate) direction: Direction, - pub(crate) export_funcs: Vec<(String, String)>, - pub(crate) methods: HashMap>, - // tracking all the exported resources used in generating the - // resource interface and the resource destructors - // this interface-level tracking is needed to prevent duplicated - // resource declaration which has been declared in other interfaces - pub(crate) exported_resources: HashSet, - pub(crate) wasm_import_module: Option<&'a str>, -} - -impl InterfaceGenerator<'_> { - pub(crate) fn define_interface_types(&mut self, id: InterfaceId) { - let mut live = LiveTypes::default(); - live.add_interface(self.resolve, id); - self.define_live_types(&live); - } - - pub(crate) fn define_function_types(&mut self, funcs: &[(&str, &Function)]) { - let mut live = LiveTypes::default(); - for (_, func) in funcs { - live.add_func(self.resolve, func); - } - self.define_live_types(&live); - } - - pub(crate) fn define_live_types(&mut self, live: &LiveTypes) { - for ty in live.iter() { - if self.gen.c_type_names.contains_key(&ty) { - continue; - } - - // add C type - let (info, encoded) = gen_type_name(&self.resolve, ty); - let mut name = match info { - CTypeNameInfo::Anonymous { is_prim: true } => self.gen.world.to_snake_case(), - _ => self.c_owner_namespace(ty), - }; - - let prev = self.gen.c_type_namespaces.insert(ty, name.clone()); - assert!(prev.is_none()); - - name.push('_'); - name.push_str(&encoded); - name.push_str("_t"); - let prev = self.gen.c_type_names.insert(ty, name.clone()); - assert!(prev.is_none()); - - // add Go types to the list - let mut name = self.owner_namespace(ty); - name.push_str(&self.ty_name(&Type::Id(ty))); - - let prev = self.gen.type_names.insert(ty, name.clone()); - assert!(prev.is_none()); - - // define Go types - match &self.resolve.types[ty].name { - Some(name) => self.define_type(name, ty), - None => self.anonymous_type(ty), - } - } - } - - /// Given a type ID, returns the namespace of the type. - pub(crate) fn owner_namespace(&self, id: TypeId) -> String { - let ty = &self.resolve.types[id]; - match (ty.owner, self.interface) { - // If this type is owned by an interface, then we must be generating - // bindings for that interface to proceed. - (TypeOwner::Interface(a), Some((b, key))) if a == b => self.interface_identifier(key), - - // If this type has no owner then it's an anonymous type. Here it's - // assigned to whatever we happen to be generating bindings for. - (TypeOwner::None, Some((_, key))) => self.interface_identifier(key), - (TypeOwner::None, None) => self.gen.world.to_upper_camel_case(), - - // If this type is owned by a world then we must not be generating - // bindings for an interface. - (TypeOwner::World(_), None) => self.gen.world.to_upper_camel_case(), - (TypeOwner::World(_), Some(_)) => unreachable!(), - (TypeOwner::Interface(_), None) => unreachable!(), - (TypeOwner::Interface(_), Some(_)) => unreachable!(), - } - } - - pub(crate) fn c_owner_namespace(&self, id: TypeId) -> String { - c_owner_namespace( - self.interface, - matches!(self.direction, Direction::Import), - self.gen.world.clone(), - self.resolve, - id, - &Default::default(), - ) - } - - /// Returns the namespace of the current interface. - /// - /// If self is not an interface, returns the namespace of the world. - pub(crate) fn namespace(&self) -> String { - match self.interface { - Some((_, key)) => self.interface_identifier(key), - None => self.gen.world.to_upper_camel_case(), - } - } - - pub(crate) fn c_namespace_of_resource(&self, id: TypeId) -> String { - self.gen.c_type_namespaces[&id].clone() - } - - /// Returns the identifier of the given interface. - pub(crate) fn interface_identifier(&self, key: &WorldKey) -> String { - match key { - WorldKey::Name(k) => k.to_upper_camel_case(), - WorldKey::Interface(id) => { - let mut name = String::new(); - if matches!(self.direction, Direction::Export) { - name.push_str("Exports"); - } - let iface = &self.resolve.interfaces[*id]; - let pkg = &self.resolve.packages[iface.package.unwrap()]; - name.push_str(&pkg.name.namespace.to_upper_camel_case()); - name.push_str(&pkg.name.name.to_upper_camel_case()); - if let Some(version) = &pkg.name.version { - let version = version.to_string().replace(['.', '-', '+'], "_"); - name.push_str(&version); - name.push('_'); - } - name.push_str(&iface.name.as_ref().unwrap().to_upper_camel_case()); - name - } - } - } - - /// Returns the function name of the given function. - pub(crate) fn func_name(&self, func: &Function) -> String { - match func.kind { - FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { - func.name.to_upper_camel_case() - } - FunctionKind::Static(_) | FunctionKind::AsyncStatic(_) => { - func.name.replace('.', " ").to_upper_camel_case() - } - FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => match self.direction { - Direction::Import => func.name.split('.').last().unwrap().to_upper_camel_case(), - Direction::Export => func.name.replace('.', " ").to_upper_camel_case(), - }, - FunctionKind::Constructor(id) => match self.direction { - Direction::Import => { - let resource_name = self.resolve.types[id].name.as_deref().unwrap(); - format!("New{}", resource_name.to_upper_camel_case()) - } - Direction::Export => func.name.replace('.', " ").to_upper_camel_case(), - }, - } - } - - /// Returns the type name of the given type. - /// - /// Type name is prefixed with the namespace of the interface. - /// If convert is true, the type name is converted to upper camel case. - /// Otherwise, the type name is not converted. - pub(crate) fn type_name(&self, ty_name: &str, convert: bool) -> String { - let mut name = String::new(); - let namespace = self.namespace(); - let ty_name = if convert { - ty_name.to_upper_camel_case() - } else { - ty_name.into() - }; - name.push_str(&namespace); - name.push_str(&ty_name); - name - } - - /// A special variable generated for exported interfaces. - /// - /// This variable is used to store the exported interface. - pub(crate) fn get_interface_var_name(&self) -> String { - self.namespace().to_snake_case() - } - - /// Returns the type representation of the given type. - /// - /// There are some special cases: - /// 1. If the type is list, the type representation is `[]`. - /// 2. If the type is option, the type representation is `Option[]`. - /// 3. If the type is result, the type representation is `Result[, ]`. - /// - /// For any other ID type, the type representation is the type name of the ID. - pub(crate) fn get_ty(&mut self, ty: &Type) -> String { - match ty { - Type::Bool => "bool".into(), - Type::U8 => "uint8".into(), - Type::U16 => "uint16".into(), - Type::U32 => "uint32".into(), - Type::U64 => "uint64".into(), - Type::S8 => "int8".into(), - Type::S16 => "int16".into(), - Type::S32 => "int32".into(), - Type::S64 => "int64".into(), - Type::F32 => "float32".into(), - Type::F64 => "float64".into(), - Type::Char => "rune".into(), - Type::String => "string".into(), - Type::ErrorContext => todo!(), - Type::Id(id) => { - let ty = &self.resolve().types[*id]; - match &ty.kind { - TypeDefKind::List(ty) => { - format!("[]{}", self.get_ty(ty)) - } - TypeDefKind::Option(o) => { - self.gen.with_result_option(true); - format!("Option[{}]", self.get_ty(o)) - } - TypeDefKind::Result(r) => { - self.gen.with_result_option(true); - format!( - "Result[{}, {}]", - self.optional_ty(r.ok.as_ref()), - self.optional_ty(r.err.as_ref()) - ) - } - _ => self.gen.type_names.get(id).unwrap().to_owned(), - } - } - } - } - - /// Returns the type name of the given type. - /// - /// This function does not prefixed the type name with the namespace of the type owner. - pub(crate) fn ty_name(&self, ty: &Type) -> String { - match ty { - Type::Bool => "Bool".into(), - Type::U8 => "U8".into(), - Type::U16 => "U16".into(), - Type::U32 => "U32".into(), - Type::U64 => "U64".into(), - Type::S8 => "S8".into(), - Type::S16 => "S16".into(), - Type::S32 => "S32".into(), - Type::S64 => "S64".into(), - Type::F32 => "F32".into(), - Type::F64 => "F64".into(), - Type::Char => "Byte".into(), - Type::String => "String".into(), - Type::ErrorContext => todo!(), - Type::Id(id) => { - let ty = &self.resolve.types[*id]; - // if a type has name, return the name - if let Some(name) = &ty.name { - return name.to_upper_camel_case(); - } - // otherwise, return the anonymous type name - match &ty.kind { - TypeDefKind::Type(t) => self.ty_name(t), - TypeDefKind::Record(_) - | TypeDefKind::Resource - | TypeDefKind::Flags(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Variant(_) => { - // these types are not anonymous, and thus have a name - unimplemented!() - } - TypeDefKind::Tuple(t) => { - let mut src = String::new(); - src.push_str("Tuple"); - src.push_str(&t.types.len().to_string()); - for ty in t.types.iter() { - src.push_str(&self.ty_name(ty)); - } - src.push('T'); - src - } - TypeDefKind::Option(t) => { - let mut src = String::new(); - src.push_str("Option"); - src.push_str(&self.ty_name(t)); - src.push('T'); - src - } - TypeDefKind::Result(r) => { - let mut src = String::new(); - src.push_str("Result"); - src.push_str(&self.optional_ty_name(r.ok.as_ref())); - src.push_str(&self.optional_ty_name(r.ok.as_ref())); - src.push('T'); - src - } - TypeDefKind::List(t) => { - let mut src = String::new(); - src.push_str("List"); - src.push_str(&self.ty_name(t)); - src.push('T'); - src - } - TypeDefKind::Future(t) => { - let mut src = String::new(); - src.push_str("Future"); - src.push_str(&self.optional_ty_name(t.as_ref())); - src.push('T'); - src - } - TypeDefKind::Stream(t) => { - let mut src = String::new(); - src.push_str("Stream"); - src.push_str(&self.optional_ty_name(t.as_ref())); - src.push('T'); - src - } - TypeDefKind::Handle(Handle::Own(ty)) => { - // Currently there is no different between Own and Borrow - // in the Go code. They are just represented as - // the name of the resource type. - let mut src = String::new(); - let ty = &self.resolve.types[*ty]; - if let Some(name) = &ty.name { - src.push_str(&name.to_upper_camel_case()); - } - src - } - TypeDefKind::Handle(Handle::Borrow(ty)) => { - let mut src = String::new(); - let ty = &self.resolve.types[*ty]; - if let Some(name) = &ty.name { - src.push_str(&name.to_upper_camel_case()); - } - src - } - TypeDefKind::Unknown => unreachable!(), - } - } - } - } - - /// Used in get_ty_name to get the type name of the given type. - pub(crate) fn optional_ty_name(&self, ty: Option<&Type>) -> String { - match ty { - Some(ty) => self.ty_name(ty), - None => "Empty".into(), - } - } - - pub(crate) fn func_params(&mut self, func: &Function) -> String { - let mut params = String::new(); - match func.kind { - FunctionKind::Method(_) => { - for (i, (name, param)) in func.params.iter().skip(1).enumerate() { - self.get_func_params_common(i, &mut params, name, param); - } - } - _ => { - for (i, (name, param)) in func.params.iter().enumerate() { - self.get_func_params_common(i, &mut params, name, param); - } - } - } - - params - } - - pub(crate) fn get_func_params_common( - &mut self, - i: usize, - params: &mut String, - name: &String, - param: &Type, - ) { - if i > 0 { - params.push_str(", "); - } - params.push_str(&avoid_keyword(&name.to_snake_case())); - params.push(' '); - params.push_str(&self.get_ty(param)); - } - - pub(crate) fn func_results(&mut self, func: &Function) -> String { - let mut results = String::new(); - results.push(' '); - if let Some(ty) = &func.result { - results.push_str(&self.get_ty(ty)); - results.push(' '); - } - results - } - - pub(crate) fn c_param( - &mut self, - src: &mut Source, - name: &str, - param: &Type, - direction: Direction, - ) { - // If direction is `Import`, this function is invoked as calling an imported function. - // The parameter uses `&` to dereference argument of pointer type. - // The & is added as a prefix to the argument name. And there is no - // type declaration needed to be added to the argument. - // - // If direction is `Export`, this function is invoked in printing export function signature. - // It uses the form of ` *C.` to print each parameter in the function, where - // * is only used if the parameter is of pointer type. - - let is_pointer = is_arg_by_pointer(self.resolve, param); - let mut prefix = String::new(); - let mut param_name = String::new(); - let mut postfix = String::new(); - - match direction { - Direction::Import => { - if is_pointer { - prefix.push_str("&"); - } - if name != "ret" { - param_name = format!("lower_{name}"); - } else { - param_name.push_str(name); - } - } - Direction::Export => { - if is_pointer { - postfix.push_str("*"); - } - param_name.push_str(name); - postfix.push_str(&self.gen.get_c_ty(param)); - } - } - src.push_str(&format!("{prefix}{param_name} {postfix}")); - } - - // Append C params to source. - pub(crate) fn c_func_params( - &mut self, - params: &mut Source, - func: &Function, - direction: Direction, - ) { - for (i, (name, param)) in func.params.iter().enumerate() { - if i > 0 { - params.push_str(", "); - } - self.c_param( - params, - &avoid_keyword(&name.to_snake_case()), - param, - direction, - ); - } - } - - pub(crate) fn c_func_returns( - &mut self, - src: &mut Source, - _resolve: &Resolve, - func: &Function, - direction: Direction, - ) { - let add_param_seperator = |src: &mut Source| { - if !func.params.is_empty() { - src.push_str(", "); - } - }; - match &func.result { - None => { - // no return - src.push_str(")"); - } - Some(return_ty) => { - // one return - if is_arg_by_pointer(self.resolve, return_ty) { - add_param_seperator(src); - self.c_param(src, "ret", return_ty, direction); - src.push_str(")"); - } else { - src.push_str(")"); - if matches!(direction, Direction::Export) { - src.push_str(&format!(" {ty}", ty = self.gen.get_c_ty(return_ty))); - } - } - } - } - } - - pub(crate) fn c_func_sig( - &mut self, - resolve: &Resolve, - func: &Function, - direction: Direction, - ) -> String { - let mut src = Source::default(); - let func_name = if matches!(direction, Direction::Import) { - c_func_name( - matches!(direction, Direction::Import), - self.resolve, - &self.gen.world, - self.interface.map(|(_, key)| key), - func, - &Default::default(), - ) - } else { - // do not want to generate public functions - format!("{}{}", self.namespace(), self.func_name(func)).to_lower_camel_case() - }; - - if matches!(direction, Direction::Export) { - src.push_str("func "); - } else { - src.push_str("C."); - } - src.push_str(&func_name); - src.push_str("("); - - // prepare args - self.c_func_params(&mut src, func, direction); - - // prepare returns - self.c_func_returns(&mut src, resolve, func, direction); - src.to_string() - } - - pub(crate) fn free_c_arg(&mut self, ty: &Type, arg: &str) -> String { - let mut ty_name = self.gen.get_c_ty(ty); - let it: Vec<&str> = ty_name.split('_').collect(); - ty_name = it[..it.len() - 1].join("_"); - format!("defer {ty_name}_free({arg})\n") - } - - // This is useful in defining functions in the exported interface that the guest needs to implement - pub(crate) fn func_sig_with_no_namespace(&mut self, func: &Function) -> String { - format!( - "{}({}){}", - self.func_name(func), - self.func_params(func), - self.func_results(func) - ) - } - - pub(crate) fn func_sig(&mut self, func: &Function) { - self.src.push_str("func "); - - match func.kind { - FunctionKind::Freestanding => { - let namespace = self.namespace(); - self.src.push_str(&namespace); - } - FunctionKind::Method(ty) => { - let ty = self.get_ty(&Type::Id(ty)); - self.src.push_str(&format!("(self {ty}) ", ty = ty)); - } - _ => {} - } - let func_sig = self.func_sig_with_no_namespace(func); - self.src.push_str(&func_sig); - self.src.push_str("{\n"); - } - - pub(crate) fn field_name(&mut self, field: &Field) -> String { - field.name.to_upper_camel_case() - } - - pub(crate) fn extract_result_ty(&self, ty: &Type) -> (Option, Option) { - //TODO: don't copy from the C code - // optimization on the C size. - // See https://github.com/bytecodealliance/wit-bindgen/pull/450 - match ty { - Type::Id(id) => match &self.resolve.types[*id].kind { - TypeDefKind::Result(r) => (r.ok, r.err), - _ => (None, None), - }, - _ => (None, None), - } - } - - pub(crate) fn extract_list_ty(&self, ty: &Type) -> Option<&Type> { - match ty { - Type::Id(id) => match &self.resolve.types[*id].kind { - TypeDefKind::List(l) => Some(l), - _ => None, - }, - _ => None, - } - } - - pub(crate) fn is_empty_tuple_ty(&self, ty: &Type) -> bool { - match ty { - Type::Id(id) => match &self.resolve.types[*id].kind { - TypeDefKind::Tuple(t) => t.types.is_empty(), - _ => false, - }, - _ => false, - } - } - - pub(crate) fn optional_ty(&mut self, ty: Option<&Type>) -> String { - match ty { - Some(ty) => self.get_ty(ty), - None => "struct{}".into(), - } - } - - pub(crate) fn anonymous_type(&mut self, ty: TypeId) { - let kind = &self.resolve.types[ty].kind; - match kind { - TypeDefKind::Type(_) - | TypeDefKind::Flags(_) - | TypeDefKind::Record(_) - | TypeDefKind::Resource - | TypeDefKind::Enum(_) - | TypeDefKind::Variant(_) => { - // no anonymous type for these types - unreachable!() - } - TypeDefKind::Tuple(t) => { - let ty_name = self.ty_name(&Type::Id(ty)); - let name = self.type_name(&ty_name, false); - - self.src.push_str(&format!("type {name} struct {{\n",)); - for (i, ty) in t.types.iter().enumerate() { - let ty = self.get_ty(ty); - self.src.push_str(&format!(" F{i} {ty}\n",)); - } - self.src.push_str("}\n\n"); - } - TypeDefKind::Option(_) | TypeDefKind::Result(_) | TypeDefKind::List(_) => { - // no anonymous type needs to be generated here because we are using - // Option[T], Result[T, E], and []T in Go - } - TypeDefKind::Handle(_) => { - // although handles are anonymous types, they are generated in the - // `type_resource` function as part of the resource type generation. - } - TypeDefKind::Future(_) => todo!("anonymous_type for future"), - TypeDefKind::Stream(_) => todo!("anonymous_type for stream"), - TypeDefKind::Unknown => unreachable!(), - } - } - - pub(crate) fn print_constructor_method_without_value(&mut self, name: &str, case_name: &str) { - uwriteln!( - self.src, - "func {name}{case_name}() {name} {{ - return {name}{{kind: {name}Kind{case_name}}} - }} - ", - ); - } - - pub(crate) fn print_accessor_methods(&mut self, name: &str, case_name: &str, ty: &Type) { - self.gen.with_fmt_import(true); - let ty = self.get_ty(ty); - uwriteln!( - self.src, - "func {name}{case_name}(v {ty}) {name} {{ - return {name}{{kind: {name}Kind{case_name}, val: v}} - }} - ", - ); - uwriteln!( - self.src, - "func (n {name}) Get{case_name}() {ty} {{ - if g, w := n.Kind(), {name}Kind{case_name}; g != w {{ - panic(fmt.Sprintf(\"Attr kind is %v, not %v\", g, w)) - }} - return n.val.({ty}) - }} - ", - ); - uwriteln!( - self.src, - "func (n *{name}) Set{case_name}(v {ty}) {{ - n.val = v - n.kind = {name}Kind{case_name} - }} - ", - ); - } - - pub(crate) fn print_kind_method(&mut self, name: &str) { - uwriteln!( - self.src, - "func (n {name}) Kind() {name}Kind {{ - return n.kind - }} - " - ); - } - - pub(crate) fn print_variant_field(&mut self, name: &str, case_name: &str, i: usize) { - if i == 0 { - self.src - .push_str(&format!(" {name}Kind{case_name} {name}Kind = iota\n",)); - } else { - self.src.push_str(&format!(" {name}Kind{case_name}\n",)); - } - } - - pub(crate) fn import(&mut self, resolve: &Resolve, func: &Function) { - let mut func_bindgen = bindgen::FunctionBindgen::new(self, func); - func_bindgen.process_args(); - func_bindgen.process_returns(); - let ret = func_bindgen.args; - let lower_src = func_bindgen.lower_src; - let lift_src = func_bindgen.lift_src; - - // // print function signature - self.func_sig(func); - - // body - // prepare args - self.src.push_str(&lower_src); - - self.import_invoke(resolve, func, &lift_src, ret); - - // return - - self.src.push_str("}\n\n"); - } - - pub(crate) fn import_invoke( - &mut self, - resolve: &Resolve, - func: &Function, - lift_src: &Source, - ret: Vec, - ) { - let invoke = self.c_func_sig(resolve, func, Direction::Import); - match &func.result { - None => { - self.src.push_str(&invoke); - self.src.push_str("\n"); - } - Some(return_ty) => { - if is_arg_by_pointer(self.resolve, return_ty) { - let c_ret_type = self.gen.get_c_ty(return_ty); - self.src.push_str(&format!("var ret {c_ret_type}\n")); - self.src.push_str(&invoke); - self.src.push_str("\n"); - } else { - self.src.push_str(&format!("ret := {invoke}\n")); - } - self.src.push_str(lift_src); - self.src.push_str(&format!("return {ret}\n", ret = ret[0])); - } - } - } - - pub(crate) fn export(&mut self, resolve: &Resolve, func: &Function) { - let mut func_bindgen = bindgen::FunctionBindgen::new(self, func); - func_bindgen.process_args(); - func_bindgen.process_returns(); - - let args = func_bindgen.args; - let ret = func_bindgen.c_args; - let lift_src = func_bindgen.lift_src; - let lower_src = func_bindgen.lower_src; - - // This variable holds the declaration functions in the exported interface that user - // needs to implement. - let interface_method_decl = self.func_sig_with_no_namespace(func); - let export_func = { - let mut src = String::new(); - // header - src.push_str("//export "); - let name = c_func_name( - matches!(self.direction, Direction::Import), - self.resolve, - &self.gen.world, - self.interface.map(|(_, key)| key), - func, - &Default::default(), - ); - src.push_str(&name); - src.push('\n'); - - // signature - src.push_str(&self.c_func_sig(resolve, func, Direction::Export)); - src.push_str(" {\n"); - - // free all the parameters - for (name, ty) in func.params.iter() { - // TODO: should test if owns anything - if false { - let free = self.free_c_arg(ty, &avoid_keyword(&name.to_snake_case())); - src.push_str(&free); - } - } - - // prepare args - - src.push_str(&lift_src); - - // invoke - let invoke = match func.kind { - FunctionKind::Method(_) => { - format!( - "lift_self.{}({})", - self.func_name(func), - args.iter() - .enumerate() - .skip(1) - .map(|(i, name)| format!( - "{}{}", - name, - if i < func.params.len() - 1 { ", " } else { "" } - )) - .collect::() - ) - } - _ => format!( - "{}.{}({})", - &self.get_interface_var_name(), - self.func_name(func), - args.iter() - .enumerate() - .map(|(i, name)| format!( - "{}{}", - name, - if i < func.params.len() - 1 { ", " } else { "" } - )) - .collect::() - ), - }; - - // prepare ret - match &func.result { - None => { - src.push_str(&format!("{invoke}\n")); - } - Some(return_ty) => { - src.push_str(&format!("result := {invoke}\n")); - src.push_str(&lower_src); - - let lower_result = &ret[0]; - if is_arg_by_pointer(self.resolve, return_ty) { - src.push_str(&format!("*ret = {lower_result}\n")); - } else { - src.push_str(&format!("return {ret}\n", ret = &ret[0])); - } - } - }; - - src.push_str("\n}\n"); - src - }; - - match func.kind { - FunctionKind::Method(id) => { - self.methods - .entry(id) - .or_default() - .push((interface_method_decl, export_func)); - } - _ => { - self.export_funcs.push((interface_method_decl, export_func)); - } - } - } - - pub(crate) fn finish(&mut self) { - if !self.export_funcs.is_empty() || !self.exported_resources.is_empty() { - let interface_var_name = &self.get_interface_var_name(); - let interface_name = &self.namespace(); - - self.src - .push_str(format!("var {interface_var_name} {interface_name} = nil\n").as_str()); - uwriteln!(self.src, - "// `Set{interface_name}` sets the `{interface_name}` interface implementation. - // This function will need to be called by the init() function from the guest application. - // It is expected to pass a guest implementation of the `{interface_name}` interface." - ); - self.src.push_str( - format!( - "func Set{interface_name}(i {interface_name}) {{\n {interface_var_name} = i\n}}\n" - ) - .as_str(), - ); - - self.print_export_interface(); - - // print resources and methods - - for id in &self.exported_resources { - // generate an interface that contains all the methods - // that the guest code needs to implement. - let ty_name = self.gen.type_names.get(id).unwrap(); - - self.src.push_str(&format!("type {ty_name} interface {{\n")); - if self.methods.get(id).is_none() { - // if this resource has no methods, generate an empty interface - // note that constructor and static methods are included in the - // top level interface definition. - self.src.push_str("}\n\n"); - } else { - // otherwise, generate an interface that contains all the methods - for (interface_func_declaration, _) in &self.methods[id] { - self.src - .push_str(format!("{interface_func_declaration}\n").as_str()); - } - self.src.push_str("}\n\n"); - - // generate each method as a private export function - for (_, export_func) in &self.methods[id] { - self.src.push_str(export_func); - } - } - } - - for (_, export_func) in &self.export_funcs { - self.src.push_str(export_func); - } - } - } - - pub(crate) fn print_export_interface(&mut self) { - let interface_name = &self.namespace(); - self.src - .push_str(format!("type {interface_name} interface {{\n").as_str()); - for (interface_func_declaration, _) in &self.export_funcs { - self.src - .push_str(format!("{interface_func_declaration}\n").as_str()); - } - self.src.push_str("}\n"); - } -} - -impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { - fn resolve(&self) -> &'a Resolve { - self.resolve - } - - fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, _docs: &Docs) { - let name = self.type_name(name, true); - self.src.push_str(&format!("type {name} struct {{\n",)); - for field in record.fields.iter() { - let ty = self.get_ty(&field.ty); - let name = self.field_name(field); - self.src.push_str(&format!(" {name} {ty}\n",)); - } - self.src.push_str("}\n\n"); - } - - fn type_resource(&mut self, id: TypeId, name: &str, _docs: &Docs) { - let type_name = self.type_name(name, true); - let private_type_name = type_name.to_snake_case(); - // for imports, generate a `int32` type for resource handle representation. - // for exports, generate a map to store unique IDs of resources to their - // resource interfaces, which are implemented by guest code. - match self.direction { - Direction::Import => { - self.src.push_str(&format!( - "// {type_name} is a handle to imported resource {name}\n" - )); - self.src.push_str(&format!("type {type_name} int32\n\n")); - let import_module = self.wasm_import_module.unwrap().to_string(); - - // generate [resource-drop] function - uwriteln!( - self.src, - "//go:wasmimport {import_module} [resource-drop]{name} - func _{type_name}_drop(self {type_name}) - - func (self {type_name}) Drop() {{ - _{type_name}_drop(self) - }} - " - ); - } - Direction::Export => { - // generate a typedef struct for export resource - let c_typedef_target = self.gen.c_type_names[&id].clone(); - let ns = self.c_namespace_of_resource(id); - let snake = self.resolve.types[id] - .name - .as_ref() - .unwrap() - .to_snake_case(); - let mut own = ns.clone(); - own.push_str("_own_"); - own.push_str(&snake); - own.push_str("_t"); - - // generate a typedef struct for export resource - // the typedef struct is a dummy struct that contains a - // Go binding specific handle field. This handle field is used - // to retrieve the exported Go struct from the exported C struct. - self.preamble - .push_str(&format!("// typedef struct {c_typedef_target} ")); - self.preamble.push_str("{"); - self.preamble.push_str("\n"); - self.preamble.push_str("// int32_t __handle; \n"); - self.preamble.push_str("// "); - self.preamble.push_str("} "); - self.preamble.push_str(&c_typedef_target); - self.preamble.push_str(";\n"); - - // import "sync" for Mutex - self.gen.with_sync_import(true); - self.src - .push_str(&format!("// resource {type_name} internal bookkeeping")); - uwriteln!( - self.src, - " - var ( - // a map of indexed {type_name} instances - // this is used to retrieve the instance from exported C resources - // This establishes a link between the exported C struct and the exported Go struct. - // This map will be recycled when the dtor is called. - {private_type_name}_pointers = make(map[int32]{type_name}) - {private_type_name}_next_id int32 = 0 - {private_type_name}_mu sync.Mutex - - // a map of {type_name} instances to their owning handlers. This is used to - // retrieve the owning handler that is necessary to implement the Drop() method. - // Note that the owning handler only exists after the constructor has been called. - // This map will be recycled when the dtor is called. - {private_type_name}_to_own_handlers sync.Map - ) - " - ); - - uwriteln!( - self.src, - " - // link the instance to its owning handler - func set{type_name}OwningHandler(self {type_name}, owningHandler int32) {{ - {private_type_name}_to_own_handlers.Store(self, owningHandler) - }} - - // get the owning handler for the instance - func get{type_name}OwningHandler(self {type_name}) int32 {{ - owningHandler, ok := {private_type_name}_to_own_handlers.Load(self) - if !ok {{ - panic(\"Internal error: owning handler not found\") - }} - return owningHandler.(int32) - }} - " - ); - - // generate [dtor] function for exported resources - let namespace = self.c_owner_namespace(id); - let snake = name.to_snake_case(); - let func_name = format!("{}_{}", namespace, snake).to_lower_camel_case(); - self.src - .push_str(&format!("//export {namespace}_{snake}_destructor\n")); - uwriteln!( - self.src, - "func {func_name}Destructor(self *C.{c_typedef_target}) {{ - {private_type_name} := {private_type_name}_pointers[int32(self.__handle)] - {private_type_name}_to_own_handlers.Delete({private_type_name}) - delete({private_type_name}_pointers, int32(self.__handle)) - C.free(unsafe.Pointer(self)) - }} - ", - ); - - self.gen.with_import_unsafe(true); - - // generate [resource-drop] function - uwriteln!( - self.src, - "func Drop{type_name}(self {type_name}) {{ - owningHandler := get{type_name}OwningHandler(self) - var cOwningHandler C.{own} - cOwningHandler.__handle = C.int32_t(owningHandler) - C.{ns}_{snake}_drop_own(cOwningHandler) - }} - ", - ); - - // book keep the exported resource type - self.exported_resources.insert(id); - self.gen.exported_resources.insert(id); - } - }; - } - - fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, _docs: &Docs) { - let name = self.type_name(name, true); - - // TODO: use flags repr to determine how many flags are needed - self.src.push_str(&format!("type {name} uint64\n")); - self.src.push_str("const (\n"); - for (i, flag) in flags.flags.iter().enumerate() { - let case_flag = flag.name.to_upper_camel_case(); - - if i == 0 { - self.src.push_str(&format!( - " {name}_{flag} {name} = 1 << iota\n", - name = name, - flag = case_flag, - )); - } else { - self.src.push_str(&format!( - " {name}_{flag}\n", - name = name, - flag = case_flag, - )); - } - } - self.src.push_str(")\n\n"); - } - - fn type_tuple(&mut self, _id: TypeId, name: &str, tuple: &Tuple, _docs: &Docs) { - let name = self.type_name(name, true); - self.src.push_str(&format!("type {name} struct {{\n",)); - for (i, case) in tuple.types.iter().enumerate() { - let ty = self.get_ty(case); - self.src.push_str(&format!("F{i} {ty}\n",)); - } - self.src.push_str("}\n\n"); - } - - fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, _docs: &Docs) { - let name = self.type_name(name, true); - // TODO: use variant's tag to determine how many cases are needed - // this will help to optmize the Kind type. - self.src.push_str(&format!("type {name}Kind int\n\n")); - self.src.push_str("const (\n"); - - for (i, case) in variant.cases.iter().enumerate() { - let case_name = case.name.to_upper_camel_case(); - self.print_variant_field(&name, &case_name, i); - } - self.src.push_str(")\n\n"); - - self.src.push_str(&format!("type {name} struct {{\n")); - self.src.push_str(&format!("kind {name}Kind\n")); - self.src.push_str("val any\n"); - self.src.push_str("}\n\n"); - - self.print_kind_method(&name); - - for case in variant.cases.iter() { - let case_name = case.name.to_upper_camel_case(); - if let Some(ty) = case.ty.as_ref() { - self.gen.with_fmt_import(true); - self.print_accessor_methods(&name, &case_name, ty); - } else { - self.print_constructor_method_without_value(&name, &case_name); - } - } - } - - fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, _docs: &Docs) { - let name = self.type_name(name, true); - // TODO: use variant's tag to determine how many cases are needed - // this will help to optmize the Kind type. - self.src.push_str(&format!("type {name}Kind int\n\n")); - self.src.push_str("const (\n"); - - for (i, case) in enum_.cases.iter().enumerate() { - let case_name = case.name.to_upper_camel_case(); - self.print_variant_field(&name, &case_name, i); - } - self.src.push_str(")\n\n"); - - self.src.push_str(&format!("type {name} struct {{\n")); - self.src.push_str(&format!("kind {name}Kind\n")); - self.src.push_str("}\n\n"); - - self.print_kind_method(&name); - - for case in enum_.cases.iter() { - let case_name = case.name.to_upper_camel_case(); - self.print_constructor_method_without_value(&name, &case_name); - } - } - - fn type_alias(&mut self, _id: TypeId, name: &str, ty: &Type, _docs: &Docs) { - let name = self.type_name(name, true); - let ty = self.get_ty(ty); - self.src.push_str(&format!("type {name} = {ty}\n")); - } - - fn type_list(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { - // no impl since these types are generated as anonymous types - } - - fn type_option(&mut self, _id: TypeId, _name: &str, _payload: &Type, _docs: &Docs) { - // no impl since these types are generated as anonymous types - } - - fn type_result(&mut self, _id: TypeId, _name: &str, _result: &Result_, _docs: &Docs) { - // no impl since these types are generated as anonymous types - } - - fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { - _ = (id, name, ty, docs); - todo!() - } - - fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { - _ = (id, name, ty, docs); - todo!() - } - - fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { - todo!("type_builtin") - } -} diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs deleted file mode 100644 index e9f514e27..000000000 --- a/crates/go/src/lib.rs +++ /dev/null @@ -1,407 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::io::{Read, Write}; -use std::mem; -use std::process::Stdio; - -use anyhow::Result; -use heck::ToSnakeCase; -use wit_bindgen_c::imported_types_used_by_exported_interfaces; -use wit_bindgen_core::wit_parser::{ - Function, InterfaceId, LiveTypes, Resolve, SizeAlign, Type, TypeId, WorldId, WorldKey, -}; -use wit_bindgen_core::{Direction, Files, Source, WorldGenerator}; - -mod bindgen; -mod imports; -mod interface; - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "clap", derive(clap::Args))] -pub struct Opts { - /// Whether or not `gofmt` is executed to format generated code. - #[cfg_attr(feature = "clap", arg(long))] - pub gofmt: bool, - - /// Rename the Go package in the generated source code. - #[cfg_attr(feature = "clap", arg(long))] - pub rename_package: Option, -} - -impl Default for Opts { - fn default() -> Self { - Self { - gofmt: true, - rename_package: None, - } // Set the default value of gofmt to true - } -} - -impl Opts { - pub fn build(&self) -> Box { - Box::new(TinyGo { - opts: self.clone(), - ..TinyGo::default() - }) - } -} - -#[derive(Default)] -pub struct TinyGo { - opts: Opts, - src: Source, - - // the parts immediately precede the import of "C" - preamble: Source, - - world: String, - - // import requirements for the generated code - import_requirements: imports::ImportRequirements, - - sizes: SizeAlign, - - // mapping from interface ID to the name of the interface - interface_names: HashMap, - - // C type names - c_type_names: HashMap, - - // C type namespaces - c_type_namespaces: HashMap, - - // Go type names - type_names: HashMap, - - // tracking all the exported resources used in generating the - // resource interface and the resource destructors - exported_resources: HashSet, - - // the world ID - world_id: Option, -} - -impl TinyGo { - fn interface<'a>( - &'a mut self, - resolve: &'a Resolve, - direction: Direction, - wasm_import_module: Option<&'a str>, - ) -> interface::InterfaceGenerator<'a> { - interface::InterfaceGenerator { - src: Source::default(), - preamble: Source::default(), - gen: self, - resolve, - interface: None, - direction, - export_funcs: Default::default(), - exported_resources: Default::default(), - methods: Default::default(), - wasm_import_module, - } - } - - fn get_c_ty(&self, ty: &Type) -> String { - let res = match ty { - Type::Bool => "bool".into(), - Type::U8 => "uint8_t".into(), - Type::U16 => "uint16_t".into(), - Type::U32 => "uint32_t".into(), - Type::U64 => "uint64_t".into(), - Type::S8 => "int8_t".into(), - Type::S16 => "int16_t".into(), - Type::S32 => "int32_t".into(), - Type::S64 => "int64_t".into(), - Type::F32 => "float".into(), - Type::F64 => "double".into(), - Type::Char => "uint32_t".into(), - Type::ErrorContext => todo!(), - Type::String => { - format!( - "{namespace}_string_t", - namespace = self.world.to_snake_case() - ) - } - Type::Id(id) => { - if let Some(name) = self.c_type_names.get(id) { - name.to_owned() - } else { - panic!("failed to find type name for {id:?}"); - } - } - }; - if res == "bool" { - return res; - } - format!("C.{res}") - } - - fn with_result_option(&mut self, needs_result_option: bool) { - self.import_requirements.needs_result_option = needs_result_option; - } - - fn with_import_unsafe(&mut self, needs_import_unsafe: bool) { - self.import_requirements.needs_import_unsafe = needs_import_unsafe; - } - - fn with_fmt_import(&mut self, needs_fmt_import: bool) { - self.import_requirements.needs_fmt_import = needs_fmt_import; - } - - pub fn with_sync_import(&mut self, needs_sync_import: bool) { - self.import_requirements.needs_sync_import = needs_sync_import; - } -} - -impl WorldGenerator for TinyGo { - fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { - self.world = self - .opts - .rename_package - .clone() - .unwrap_or_else(|| resolve.worlds[world].name.clone()); - self.sizes.fill(resolve); - self.world_id = Some(world); - } - - fn import_interface( - &mut self, - resolve: &Resolve, - name: &WorldKey, - id: InterfaceId, - _files: &mut Files, - ) -> Result<()> { - let name_raw = &resolve.name_world_key(name); - self.src - .push_str(&format!("// Import functions from {name_raw}\n")); - self.interface_names.insert(id, name.clone()); - - let mut gen = self.interface(resolve, Direction::Import, Some(name_raw)); - gen.interface = Some((id, name)); - gen.define_interface_types(id); - - for (_name, func) in resolve.interfaces[id].functions.iter() { - gen.import(resolve, func); - } - - let src = mem::take(&mut gen.src); - let preamble = mem::take(&mut gen.preamble); - self.src.push_str(&src); - self.preamble.append_src(&preamble); - - Ok(()) - } - - fn import_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) { - let name = &resolve.worlds[world].name; - self.src - .push_str(&format!("// Import functions from {name}\n")); - - let mut gen = self.interface(resolve, Direction::Import, Some("$root")); - gen.define_function_types(funcs); - - for (_name, func) in funcs.iter() { - gen.import(resolve, func); - } - let src = mem::take(&mut gen.src); - let preamble = mem::take(&mut gen.preamble); - self.src.push_str(&src); - self.preamble.append_src(&preamble); - } - - fn pre_export_interface(&mut self, resolve: &Resolve, _files: &mut Files) -> Result<()> { - let world = self.world_id.unwrap(); - let live_import_types = imported_types_used_by_exported_interfaces(resolve, world); - self.c_type_namespaces - .retain(|k, _| live_import_types.contains(k)); - self.c_type_names - .retain(|k, _| live_import_types.contains(k)); - self.type_names.retain(|k, _| live_import_types.contains(k)); - Ok(()) - } - - fn export_interface( - &mut self, - resolve: &Resolve, - name: &WorldKey, - id: InterfaceId, - _files: &mut Files, - ) -> Result<()> { - self.interface_names.insert(id, name.clone()); - let name_raw = &resolve.name_world_key(name); - self.src - .push_str(&format!("// Export functions from {name_raw}\n")); - - let mut gen = self.interface(resolve, Direction::Export, None); - gen.interface = Some((id, name)); - gen.define_interface_types(id); - - for (_name, func) in resolve.interfaces[id].functions.iter() { - gen.export(resolve, func); - } - - gen.finish(); - - let src = mem::take(&mut gen.src); - let preamble = mem::take(&mut gen.preamble); - self.src.push_str(&src); - self.preamble.append_src(&preamble); - Ok(()) - } - - fn export_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) -> Result<()> { - let name = &resolve.worlds[world].name; - self.src - .push_str(&format!("// Export functions from {name}\n")); - - let mut gen = self.interface(resolve, Direction::Export, None); - gen.define_function_types(funcs); - - for (_name, func) in funcs.iter() { - gen.export(resolve, func); - } - - gen.finish(); - - let src = mem::take(&mut gen.src); - let preamble = mem::take(&mut gen.preamble); - self.src.push_str(&src); - self.preamble.append_src(&preamble); - Ok(()) - } - - fn import_types( - &mut self, - resolve: &Resolve, - _world: WorldId, - types: &[(&str, TypeId)], - _files: &mut Files, - ) { - let mut gen = self.interface(resolve, Direction::Import, Some("$root")); - let mut live = LiveTypes::default(); - for (_, id) in types { - live.add_type_id(resolve, *id); - } - gen.define_live_types(&live); - let src = mem::take(&mut gen.src); - self.src.push_str(&src); - } - - fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { - // make sure all types are defined on top of the file - let src = mem::take(&mut self.src); - self.src.push_str(&src); - - // prepend package and imports header - let src = mem::take(&mut self.src); - wit_bindgen_core::generated_preamble(&mut self.src, env!("CARGO_PKG_VERSION")); - let snake = avoid_keyword(self.world.to_snake_case().as_str()).to_owned(); - // add package - self.src.push_str("package "); - self.src.push_str(&snake); - self.src.push_str("\n\n"); - - // import C - self.src.push_str("// #include \""); - self.src.push_str(self.world.to_snake_case().as_str()); - self.src.push_str(".h\"\n"); - self.src.push_str("// #include \n"); - if self.preamble.len() > 0 { - self.src.append_src(&self.preamble); - } - self.src.push_str("import \"C\"\n"); - let world = self.world.to_snake_case(); - - self.import_requirements - .generate(snake, files, format!("{}_types.go", world)); - self.src.push_str(&self.import_requirements.src); - - self.src.push_str(&src); - - if self.opts.gofmt { - let mut child = std::process::Command::new("gofmt") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("failed to spawn gofmt"); - child - .stdin - .take() - .unwrap() - .write_all(self.src.as_bytes()) - .expect("failed to write to gofmt"); - self.src.as_mut_string().truncate(0); - child - .stdout - .take() - .unwrap() - .read_to_string(self.src.as_mut_string()) - .expect("failed to read from gofmt"); - let status = child.wait().expect("failed to wait on gofmt"); - assert!(status.success()); - } - files.push(&format!("{}.go", world), self.src.as_bytes()); - - let mut opts = wit_bindgen_c::Opts::default(); - opts.no_sig_flattening = true; - opts.no_object_file = true; - opts.rename_world = self.opts.rename_package.clone(); - opts.build() - .generate(resolve, id, files) - .expect("C generator should be infallible"); - - Ok(()) - } -} - -fn avoid_keyword(s: &str) -> String { - if GOKEYWORDS.contains(&s) { - format!("_{s}") - } else { - s.into() - } -} - -// a list of Go keywords -const GOKEYWORDS: [&str; 26] = [ - "break", - "default", - "func", - "interface", - "select", - "case", - "defer", - "go", - "map", - "struct", - "chan", - "else", - "goto", - "package", - "switch", - "const", - "fallthrough", - "if", - "range", - "type", - "continue", - "for", - "import", - "return", - "var", - // not a Go keyword but needs to escape due to - // it's used as a variable name that passes to C - "ret", -]; diff --git a/crates/go/tests/codegen.rs b/crates/go/tests/codegen.rs deleted file mode 100644 index daa437658..000000000 --- a/crates/go/tests/codegen.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::io::prelude::*; -use std::io::BufReader; -use std::path::Path; -use std::process::Command; - -use heck::*; - -macro_rules! codegen_test { - (issue668 $name:tt $test:tt) => {}; - (multiversion $name:tt $test:tt) => {}; - - // TODO: implement support for stream, future, and error-context, and then - // remove these lines: - (streams $name:tt $test:tt) => {}; - (futures $name:tt $test:tt) => {}; - (resources_with_streams $name:tt $test:tt) => {}; - (resources_with_futures $name:tt $test:tt) => {}; - (error_context $name:tt $test:tt) => {}; - - ($id:ident $name:tt $test:tt) => { - #[test] - fn $id() { - test_helpers::run_world_codegen_test( - "guest-go", - $test.as_ref(), - |resolve, world, files| { - wit_bindgen_go::Opts::default() - .build() - .generate(resolve, world, files) - .unwrap() - }, - verify, - ) - } - }; -} - -test_helpers::codegen_tests!(); - -fn verify(dir: &Path, name: &str) { - let name = name.to_snake_case(); - let main = dir.join(format!("{name}.go")); - - // The generated go package is named after the world's name. - // But tinygo currently does not support non-main package and requires - // a `main()` function in the module to compile. - // The following code replaces the package name to `package main` and - // adds a `func main() {}` function at the bottom of the file. - - // TODO: However, there is still an issue. Since the go module does not - // invoke the imported functions, they will be skipped by the compiler. - // This will weaken the test's ability to verify imported functions - let file = std::fs::OpenOptions::new() - .read(true) - .write(true) - .open(&main) - .expect("failed to open file"); - let mut reader = BufReader::new(file); - let mut buf = Vec::new(); - reader.read_until(b'\n', &mut buf).unwrap(); - // Skip over `package $WORLD` line - reader.read_until(b'\n', &mut Vec::new()).unwrap(); - buf.append(&mut "package main\n".as_bytes().to_vec()); - - // check if {name}_types.go exists - let types_file = dir.join(format!("{name}_types.go")); - if std::fs::metadata(types_file).is_ok() { - // create a directory called option and move the type file to option - std::fs::create_dir(dir.join("option")).expect("Failed to create directory"); - std::fs::rename( - dir.join(format!("{name}_types.go")), - dir.join("option").join(format!("{name}_types.go")), - ) - .expect("Failed to move file"); - buf.append(&mut format!("import . \"{name}/option\"\n").as_bytes().to_vec()); - } - - reader.read_to_end(&mut buf).expect("Failed to read file"); - buf.append(&mut "func main() {}".as_bytes().to_vec()); - std::fs::write(&main, buf).expect("Failed to write to file"); - - // create go.mod file - let mod_file = dir.join("go.mod"); - let mut file = std::fs::File::create(mod_file).expect("Failed to create file go.mod"); - file.write_all(format!("module {name}\n\ngo 1.20").as_bytes()) - .expect("Failed to write to file"); - - // run tinygo on Dir directory - - let mut cmd = Command::new("tinygo"); - cmd.arg("build"); - cmd.arg("-target=wasi"); - cmd.arg("-o"); - cmd.arg("go.wasm"); - cmd.arg(format!("{name}.go")); - cmd.current_dir(dir); - test_helpers::run_command(&mut cmd); -} diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 92c4503f2..55afe5daa 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -47,11 +47,9 @@ enum Opt { args: Common, }, - /// Generates bindings for TinyGo-based Go guest modules. + /// Generates bindings for TinyGo-based Go guest modules (Deprecated) #[cfg(feature = "go")] TinyGo { - #[clap(flatten)] - opts: wit_bindgen_go::Opts, #[clap(flatten)] args: Common, }, @@ -124,7 +122,9 @@ fn main() -> Result<()> { #[cfg(feature = "rust")] Opt::Rust { opts, args } => (opts.build(), args), #[cfg(feature = "go")] - Opt::TinyGo { opts, args } => (opts.build(), args), + Opt::TinyGo { args: _ } => { + bail!("Go bindgen has been moved to a separate repository. Please visit https://github.com/bytecodealliance/go-modules for the new Go bindings generator `wit-bindgen-go`.") + } #[cfg(feature = "csharp")] Opt::CSharp { opts, args } => (opts.build(), args), }; diff --git a/tests/README.md b/tests/README.md index c3b413207..8b2858190 100644 --- a/tests/README.md +++ b/tests/README.md @@ -7,7 +7,6 @@ There are a few pre-requisites to testing the project. You only need the languag - `curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-22/wasi-sdk-22.0-linux.tar.gz` - Create an environment variable called `WASI_SDK_PATH`` giving the path where you extracted the WASI SDK download, i.e., the directory containing `bin`/`lib`/`share`` folders. - Compilers for the target language: - - Go + TinyGo - https://tinygo.org/ (v0.27.0+) - Rust - wasi target: `rustup target add wasm32-wasip1` - Java - TeaVM-WASI `ci/download-teamvm.sh` - C - [Clang](https://clang.llvm.org/) @@ -19,19 +18,19 @@ There are two suites of tests: [codegen](#testing-wit-bindgen---codegen) and [ru cargo test --workspace ``` -To run just `codegen` tests for a single language (replace rust with language of choice: `go`, `c`, `csharp`, etc.): +To run just `codegen` tests for a single language (replace rust with language of choice: `c`, `csharp`, etc.): ``` cargo test -p wit-bindgen-rust ``` -To run just `codegen` tests for a single language (replace rust with language of choice: `go`, `c`, `csharp`, etc.) and a single wit file (replace `flags` with whatever wit file should be tested): +To run just `codegen` tests for a single language (replace rust with language of choice: `c`, `csharp`, etc.) and a single wit file (replace `flags` with whatever wit file should be tested): ``` cargo test -p wit-bindgen-rust -- flags ``` -To run just `runtime` tests for a single language (replace rust with language of choice: `go`, `c`, `csharp`, etc.): +To run just `runtime` tests for a single language (replace rust with language of choice: `c`, `csharp`, etc.): ```bash cargo test -p wit-bindgen-cli --no-default-features -F rust diff --git a/tests/runtime/flavorful/wasm.go b/tests/runtime/flavorful/wasm.go deleted file mode 100644 index 89bf09bfa..000000000 --- a/tests/runtime/flavorful/wasm.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - . "wit_flavorful_go/gen" -) - -func init() { - n := &FlavorfulImpl{} - SetFlavorful(n) - SetExportsTestFlavorfulTest(n) -} - -type FlavorfulImpl struct{} - -func (f FlavorfulImpl) TestImports() { - - TestFlavorfulTestFListInRecord1(TestFlavorfulTestListInRecord1{"list_in_record1"}) - if TestFlavorfulTestFListInRecord2().A != "list_in_record2" { - panic("TestFlavorfulTestFListInRecord2") - } - if TestFlavorfulTestFListInRecord3(TestFlavorfulTestListInRecord3{"list_in_record3 input"}).A != "list_in_record3 output" { - panic("TestFlavorfulTestFListInRecord3") - } - if TestFlavorfulTestFListInRecord4(TestFlavorfulTestListInAlias{"input4"}).A != "result4" { - panic("TestFlavorfulTestFListInRecord4") - } - - var b Result[struct{}, string] - b.SetErr("bar") - TestFlavorfulTestFListInVariant1(Some[string]("foo"), b) - if TestFlavorfulTestFListInVariant2().Unwrap() != "list_in_variant2" { - panic("TestFlavorfulTestFListInVariant2") - } - if TestFlavorfulTestFListInVariant3(Some[string]("input3")).Unwrap() != "output3" { - panic("TestFlavorfulTestFListInVariant3") - } - if !TestFlavorfulTestErrnoResult().IsErr() { - panic("TestFlavorfulTestErrnoResult") - } - TestFlavorfulTestMyErrnoA() - // TODO: be able to print my_error_a - - if !TestFlavorfulTestErrnoResult().IsOk() { - panic("TestFlavorfulTestErrnoResult") - } - - t1, t2 := TestFlavorfulTestListTypedefs("typedef1", []string{"typedef2"}) - if len(t1) != 8 { - panic("TestFlavorfulTestListTypedefs") - } - if len(t2) != 1 { - panic("TestFlavorfulTestListTypedefs") - } - if t2[0] != "typedef4" { - panic("TestFlavorfulTestListTypedefs") - } - - var v2_ok Result[struct{}, struct{}] - v2_ok.Set(struct{}{}) - var v2_err Result[struct{}, struct{}] - v2_err.SetErr(struct{}{}) - - v1, v2, v3 := TestFlavorfulTestListOfVariants( - []bool{true, false}, - []Result[struct{}, struct{}]{v2_ok, v2_err}, - []TestFlavorfulTestMyErrno{TestFlavorfulTestMyErrnoSuccess(), TestFlavorfulTestMyErrnoA()}, - ) - if v1[0] != false { - panic("TestFlavorfulTestListOfVariants") - } - if v1[1] != true { - panic("TestFlavorfulTestListOfVariants") - } - if v2[0].IsOk() { - panic("TestFlavorfulTestListOfVariants") - } - if v2[1].IsErr() { - panic("TestFlavorfulTestListOfVariants") - } - if v3[0].Kind() != TestFlavorfulTestMyErrnoKindA { - panic("TestFlavorfulTestListOfVariants") - } - if v3[1].Kind() != TestFlavorfulTestMyErrnoKindB { - panic("TestFlavorfulTestListOfVariants") - } - -} - -func (f FlavorfulImpl) FListInRecord1(a ExportsTestFlavorfulTestListInRecord1) { - if a.A != "list_in_record1" { - panic("FListInRecord1") - } -} - -func (f FlavorfulImpl) FListInRecord2() ExportsTestFlavorfulTestListInRecord2 { - return ExportsTestFlavorfulTestListInRecord2{"list_in_record2"} -} - -func (f FlavorfulImpl) FListInRecord3(a ExportsTestFlavorfulTestListInRecord3) ExportsTestFlavorfulTestListInRecord3 { - if a.A != "list_in_record3 input" { - panic("FListInRecord3") - } - return ExportsTestFlavorfulTestListInRecord3{"list_in_record3 output"} -} - -func (f FlavorfulImpl) FListInRecord4(a ExportsTestFlavorfulTestListInRecord4) ExportsTestFlavorfulTestListInRecord4 { - if a.A != "input4" { - panic("FListInRecord4") - } - return ExportsTestFlavorfulTestListInRecord4{"result4"} -} - -func (f FlavorfulImpl) FListInVariant1(a Option[string], b Result[struct{}, string]) { - if a.Unwrap() != "foo" { - panic("FListInVariant1") - } - if b.UnwrapErr() != "bar" { - panic("FListInVariant1") - } -} - -func (f FlavorfulImpl) FListInVariant2() Option[string] { - return Some[string]("list_in_variant2") -} - -func (f FlavorfulImpl) FListInVariant3(a Option[string]) Option[string] { - if a.Unwrap() != "input3" { - panic("FListInVariant3") - } - return Some[string]("output3") -} - -func (f FlavorfulImpl) ErrnoResult() Result[struct{}, ExportsTestFlavorfulTestMyErrno] { - var res Result[struct{}, ExportsTestFlavorfulTestMyErrno] - res.SetErr(ExportsTestFlavorfulTestMyErrnoB()) - return res -} - -func (f FlavorfulImpl) ListTypedefs(a string, c []string) ([]uint8, []string) { - if a != "typedef1" { - panic("ListTypedefs") - } - if len(c) != 1 { - panic("ListTypedefs") - } - if c[0] != "typedef2" { - panic("ListTypedefs") - } - return []uint8("typedef3"), []string{"typedef4"} -} - -func (f FlavorfulImpl) ListOfVariants(a []bool, b []Result[struct{}, struct{}], c []ExportsTestFlavorfulTestMyErrno) ([]bool, []Result[struct{}, struct{}], []ExportsTestFlavorfulTestMyErrno) { - return a, b, c -} - -func main() {} diff --git a/tests/runtime/lists/wasm.go b/tests/runtime/lists/wasm.go deleted file mode 100644 index fa39721d0..000000000 --- a/tests/runtime/lists/wasm.go +++ /dev/null @@ -1,269 +0,0 @@ -package main - -import ( - "math" - "math/rand" - "strconv" - . "wit_lists_go/gen" -) - -func init() { - a := ListImpl{} - SetLists(a) - SetExportsTestListsTest(a) -} - -type ListImpl struct { -} - -func (i ListImpl) TestImports() { - TestListsTestEmptyListParam([]uint8{}) - TestListsTestEmptyStringParam("") - res := TestListsTestEmptyListResult() - if len(res) != 0 { - panic("TestListsTestEmptyListResult") - } - res2 := TestListsTestEmptyStringResult() - if res2 != "" { - panic("TestListsTestEmptyStringResult") - } - TestListsTestListParam([]uint8{1, 2, 3, 4}) - TestListsTestListParam2("foo") - TestListsTestListParam3([]string{"foo", "bar", "baz"}) - TestListsTestListParam4([][]string{{"foo", "bar"}, {"baz"}}) - - randomStrings := make([]string, 1000) - for i := 0; i < 1000; i++ { - randomStrings[i] = "str" + strconv.Itoa(rand.Intn(1000)) - } - TestListsTestListParamLarge(randomStrings) - res3 := TestListsTestListResult() - if len(res3) != 5 { - panic("TestListsTestListResult") - } - for i := range res3 { - if res3[i] != uint8(i+1) { - panic("TestListsTestListResult") - } - } - res4 := TestListsTestListResult2() - if res4 != "hello!" { - panic("TestListsTestListResult2") - } - res5 := TestListsTestListResult3() - if len(res5) != 2 { - panic("TestListsTestListResult3") - } - if res5[0] != "hello," { - panic("TestListsTestListResult3") - } - if res5[1] != "world!" { - panic("TestListsTestListResult3") - } - - res6 := TestListsTestListRoundtrip([]uint8{}) - if len(res6) != 0 { - panic("TestListsTestListRoundtrip") - } - res7 := TestListsTestListRoundtrip([]uint8{1, 2, 3, 4, 5}) - if len(res7) != 5 { - panic("TestListsTestListRoundtrip") - } - - res8 := TestListsTestStringRoundtrip("") - if res8 != "" { - panic("TestListsTestStringRoundtrip") - } - res9 := TestListsTestStringRoundtrip("hello ⚑ world") - if res9 != "hello ⚑ world" { - panic("TestListsTestStringRoundtrip") - } - - ret8 := TestListsTestListMinmax8([]uint8{0, math.MaxUint8}, []int8{math.MinInt8, math.MaxInt8}) - if ret8.F0[0] != uint8(0) { - panic("TestListsTestListMinmax8") - } - if ret8.F0[1] != math.MaxUint8 { - panic("TestListsTestListMinmax8") - } - if ret8.F1[0] != math.MinInt8 { - panic("TestListsTestListMinmax8") - } - if ret8.F1[1] != math.MaxInt8 { - panic("TestListsTestListMinmax8") - } - - ret16 := TestListsTestListMinmax16([]uint16{0, math.MaxUint16}, []int16{math.MinInt16, math.MaxInt16}) - if ret16.F0[0] != uint16(0) { - panic("TestListsTestListMinmax16") - } - if ret16.F0[1] != math.MaxUint16 { - panic("TestListsTestListMinmax16") - } - if ret16.F1[0] != math.MinInt16 { - panic("TestListsTestListMinmax16") - } - if ret16.F1[1] != math.MaxInt16 { - panic("TestListsTestListMinmax16") - } - - ret32 := TestListsTestListMinmax32([]uint32{0, math.MaxUint32}, []int32{math.MinInt32, math.MaxInt32}) - if ret32.F0[0] != uint32(0) { - panic("TestListsTestListMinmax32") - } - if ret32.F0[1] != math.MaxUint32 { - panic("TestListsTestListMinmax32") - } - if ret32.F1[0] != math.MinInt32 { - panic("TestListsTestListMinmax32") - } - if ret32.F1[1] != math.MaxInt32 { - panic("TestListsTestListMinmax32") - } - - ret64 := TestListsTestListMinmax64([]uint64{0, math.MaxUint64}, []int64{math.MinInt64, math.MaxInt64}) - if ret64.F0[0] != uint64(0) { - panic("TestListsTestListMinmax64") - } - if ret64.F0[1] != math.MaxUint64 { - panic("TestListsTestListMinmax64") - } - if ret64.F1[0] != math.MinInt64 { - panic("TestListsTestListMinmax64") - } - if ret64.F1[1] != math.MaxInt64 { - panic("TestListsTestListMinmax64") - } - -} - -func (i ListImpl) AllocatedBytes() uint32 { - return 0 -} - -func (i ListImpl) EmptyListParam(a []uint8) { - if len(a) != 0 { - panic("EmptyListParam") - } -} - -func (i ListImpl) EmptyStringParam(a string) { - if a != "" { - panic("EmptyStringParam") - } -} - -func (i ListImpl) EmptyListResult() []uint8 { - return []uint8{} -} - -func (i ListImpl) EmptyStringResult() string { - return "" -} - -func (i ListImpl) ListParam(a []uint8) { - if len(a) != 4 { - panic("ListParam") - } - for i := range a { - if a[i] != uint8(i+1) { - panic("ListParam") - } - } -} - -func (i ListImpl) ListParam2(a string) { - if a != "foo" { - panic("ListParam2") - } -} - -func (i ListImpl) ListParam3(a []string) { - if len(a) != 3 { - panic("ListParam3") - } - if a[0] != "foo" { - panic("ListParam3") - } - if a[1] != "bar" { - panic("ListParam3") - } - if a[2] != "baz" { - panic("ListParam3") - } -} - -func (i ListImpl) ListParam4(a [][]string) { - if len(a) != 2 { - panic("ListParam4") - } - if a[0][0] != "foo" { - panic("ListParam4") - } - if a[0][1] != "bar" { - panic("ListParam4") - } - if a[1][0] != "baz" { - panic("ListParam4") - } -} - -func (i ListImpl) ListParam5(a []ExportsTestListsTestTuple3U8U32U8T) { - if len(a) != 2 { - panic("ListParam5") - } - if a[0].F0 != 1 || a[0].F1 != 2 || a[0].F2 != 3 { - panic("ListParam5") - } - if a[1].F0 != 4 || a[1].F1 != 5 || a[1].F2 != 6 { - panic("ListParam5") - } -} - -func (i ListImpl) ListParamLarge(a []string) { - if len(a) != 1000 { - panic("ListParamLarge") - } -} - -func (i ListImpl) ListResult() []uint8 { - return []uint8{1, 2, 3, 4, 5} -} - -func (i ListImpl) ListResult2() string { - return "hello!" -} - -func (i ListImpl) ListResult3() []string { - return []string{"hello,", "world!"} -} - -func (i ListImpl) ListMinmax8(a []uint8, b []int8) ExportsTestListsTestTuple2ListU8TListS8TT { - return ExportsTestListsTestTuple2ListU8TListS8TT{a, b} -} - -func (i ListImpl) ListMinmax16(a []uint16, b []int16) ExportsTestListsTestTuple2ListU16TListS16TT { - return ExportsTestListsTestTuple2ListU16TListS16TT{a, b} -} - -func (i ListImpl) ListMinmax32(a []uint32, b []int32) ExportsTestListsTestTuple2ListU32TListS32TT { - return ExportsTestListsTestTuple2ListU32TListS32TT{a, b} -} - -func (i ListImpl) ListMinmax64(a []uint64, b []int64) ExportsTestListsTestTuple2ListU64TListS64TT { - return ExportsTestListsTestTuple2ListU64TListS64TT{a, b} -} - -func (i ListImpl) ListMinmaxFloat(a []float32, b []float64) ExportsTestListsTestTuple2ListF32TListF64TT { - return ExportsTestListsTestTuple2ListF32TListF64TT{a, b} -} - -func (i ListImpl) ListRoundtrip(a []uint8) []uint8 { - return a -} - -func (i ListImpl) StringRoundtrip(a string) string { - return a -} - -func main() {} diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index e86fbac70..44204c436 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -121,7 +121,6 @@ fn tests(name: &str, dir_name: &str) -> Result> { let mut rust = Vec::new(); let mut c = Vec::new(); let mut java = Vec::new(); - let mut go = Vec::new(); let mut c_sharp: Vec = Vec::new(); for file in dir.read_dir()? { let path = file?.path(); @@ -129,7 +128,12 @@ fn tests(name: &str, dir_name: &str) -> Result> { Some("c") => c.push(path), Some("java") => java.push(path), Some("rs") => rust.push(path), - Some("go") => go.push(path), + Some("go") => { + // Go implementation has been moved to a separate repository + println!( + "Skipping Go test as Go implementation has been moved to a separate repository" + ); + } Some("cs") => c_sharp.push(path), _ => {} } @@ -265,87 +269,6 @@ fn tests(name: &str, dir_name: &str) -> Result> { } } - // FIXME: need to fix flaky Go test - #[cfg(feature = "go")] - if !go.is_empty() && name != "flavorful" { - let (resolve, world) = resolve_wit_dir(&dir); - let world_name = &resolve.worlds[world].name; - let out_dir = out_dir.join(format!("go-{}", world_name)); - let snake = world_name.replace("-", "_"); - drop(fs::remove_dir_all(&out_dir)); - - let mut files = Default::default(); - wit_bindgen_go::Opts::default() - .build() - .generate(&resolve, world, &mut files) - .unwrap(); - let gen_dir = out_dir.join("gen"); - fs::create_dir_all(&gen_dir).unwrap(); - for (file, contents) in files.iter() { - let dst = gen_dir.join(file); - fs::write(dst, contents).unwrap(); - } - for go_impl in &go { - fs::copy(&go_impl, out_dir.join(format!("{snake}.go"))).unwrap(); - } - - let go_mod = format!("module wit_{snake}_go\n\ngo 1.20"); - fs::write(out_dir.join("go.mod"), go_mod).unwrap(); - - let out_wasm = out_dir.join("go.wasm"); - - let mut cmd = Command::new("tinygo"); - cmd.arg("build"); - cmd.arg("-target=wasi"); - cmd.arg("-o"); - cmd.arg(&out_wasm); - cmd.arg(format!("{snake}.go")); - cmd.current_dir(&out_dir); - let command = format!("{cmd:?}"); - let output = match cmd.output() { - Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), - }; - - if !output.status.success() { - println!("dir: {}", out_dir.display()); - println!("status: {}", output.status); - println!("stdout: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stderr)); - panic!("failed to compile"); - } - - // Translate the canonical ABI module into a component. - - let mut module = fs::read(&out_wasm).expect("failed to read wasm file"); - let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?; - - let section = wasm_encoder::CustomSection { - name: Cow::Borrowed("component-type"), - data: Cow::Borrowed(&encoded), - }; - module.push(section.id()); - section.encode(&mut module); - - let component = ComponentEncoder::default() - .module(module.as_slice()) - .expect("pull custom sections from module") - .validate(true) - .adapter("wasi_snapshot_preview1", &wasi_adapter) - .expect("adapter failed to get loaded") - .encode() - .expect(&format!( - "module {:?} can't be translated to a component", - out_wasm - )); - let component_path = out_wasm.with_extension("component.wasm"); - fs::write(&component_path, component).expect("write component to disk"); - - result.push(component_path); - } - #[cfg(feature = "csharp-mono")] if cfg!(windows) && !c_sharp.is_empty() { let (resolve, world) = resolve_wit_dir(&dir); diff --git a/tests/runtime/many_arguments/wasm.go b/tests/runtime/many_arguments/wasm.go deleted file mode 100644 index 03e08bed8..000000000 --- a/tests/runtime/many_arguments/wasm.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - . "wit_many_arguments_go/gen" -) - -func init() { - n := &ManyArgumentsImpl{} - SetManyArguments(n) -} - -type ManyArgumentsImpl struct{} - -func (m *ManyArgumentsImpl) ManyArguments(a1 uint64, a2 uint64, a3 uint64, a4 uint64, a5 uint64, a6 uint64, a7 uint64, a8 uint64, a9 uint64, a10 uint64, a11 uint64, a12 uint64, a13 uint64, a14 uint64, a15 uint64, a16 uint64) { - assert_eq(a1, 1) - assert_eq(a2, 2) - assert_eq(a3, 3) - assert_eq(a4, 4) - assert_eq(a5, 5) - assert_eq(a6, 6) - assert_eq(a7, 7) - assert_eq(a8, 8) - assert_eq(a9, 9) - assert_eq(a10, 10) - assert_eq(a11, 11) - assert_eq(a12, 12) - assert_eq(a13, 13) - assert_eq(a14, 14) - assert_eq(a15, 15) - assert_eq(a16, 16) - ImportsManyArguments(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) -} - -func assert_eq(a uint64, b uint64) { - if a != b { - panic("assertion failed") - } -} -func main() {} diff --git a/tests/runtime/numbers/wasm.go b/tests/runtime/numbers/wasm.go deleted file mode 100644 index 9b45e584c..000000000 --- a/tests/runtime/numbers/wasm.go +++ /dev/null @@ -1,202 +0,0 @@ -package main - -import ( - "math" - - . "wit_numbers_go/gen" -) - -func init() { - n := &NumbersImpl{} - SetNumbers(n) - SetExportsTestNumbersTest(n) -} - -type NumbersImpl struct { - scalar uint32 -} - -func (i NumbersImpl) TestImports() { - if TestNumbersTestRoundtripU8(1) != 1 { - panic("roundtrip-u8") - } - if TestNumbersTestRoundtripU8(0) != 0 { - panic("roundtrip-u8") - } - if TestNumbersTestRoundtripU8(math.MaxUint8) != math.MaxUint8 { - panic("roundtrip-u8") - } - - if TestNumbersTestRoundtripS8(1) != 1 { - panic("roundtrip-s8") - } - if TestNumbersTestRoundtripS8(math.MaxInt8) != math.MaxInt8 { - panic("roundtrip-s8") - } - if TestNumbersTestRoundtripS8(math.MinInt8) != math.MinInt8 { - panic("roundtrip-s8") - } - - if TestNumbersTestRoundtripU16(1) != 1 { - panic("roundtrip-u16") - } - if TestNumbersTestRoundtripU16(0) != 0 { - panic("roundtrip-u16") - } - if TestNumbersTestRoundtripU16(math.MaxUint16) != math.MaxUint16 { - panic("roundtrip-u16") - } - - if TestNumbersTestRoundtripS16(1) != 1 { - panic("roundtrip-s16") - } - if TestNumbersTestRoundtripS16(math.MaxInt16) != math.MaxInt16 { - panic("roundtrip-s16") - } - if TestNumbersTestRoundtripS16(math.MinInt16) != math.MinInt16 { - panic("roundtrip-s16") - } - - if TestNumbersTestRoundtripU32(1) != 1 { - panic("roundtrip-u32") - } - if TestNumbersTestRoundtripU32(0) != 0 { - panic("roundtrip-u32") - } - if TestNumbersTestRoundtripU32(math.MaxUint32) != math.MaxUint32 { - panic("roundtrip-u32") - } - - if TestNumbersTestRoundtripS32(1) != 1 { - panic("roundtrip-s32") - } - if TestNumbersTestRoundtripS32(math.MaxInt32) != math.MaxInt32 { - panic("roundtrip-s32") - } - if TestNumbersTestRoundtripS32(math.MinInt32) != math.MinInt32 { - panic("roundtrip-s32") - } - - if TestNumbersTestRoundtripU64(1) != 1 { - panic("roundtrip-u64") - } - if TestNumbersTestRoundtripU64(0) != 0 { - panic("roundtrip-u64") - } - if TestNumbersTestRoundtripU64(math.MaxUint64) != math.MaxUint64 { - panic("roundtrip-u64") - } - - if TestNumbersTestRoundtripS64(1) != 1 { - panic("roundtrip-s64") - } - if TestNumbersTestRoundtripS64(math.MaxInt64) != math.MaxInt64 { - panic("roundtrip-s64") - } - if TestNumbersTestRoundtripS64(math.MinInt64) != math.MinInt64 { - panic("roundtrip-s64") - } - - if TestNumbersTestRoundtripF32(1.0) != 1.0 { - panic("roundtrip-f32") - } - if TestNumbersTestRoundtripF32(math.MaxFloat32) != math.MaxFloat32 { - panic("roundtrip-f32") - } - if TestNumbersTestRoundtripF32(math.SmallestNonzeroFloat32) != math.SmallestNonzeroFloat32 { - panic("roundtrip-f32") - } - - if TestNumbersTestRoundtripF64(1.0) != 1.0 { - panic("roundtrip-f64") - } - if TestNumbersTestRoundtripF64(math.MaxFloat64) != math.MaxFloat64 { - panic("roundtrip-f64") - } - if TestNumbersTestRoundtripF64(math.SmallestNonzeroFloat64) != math.SmallestNonzeroFloat64 { - panic("roundtrip-f64") - } - if !math.IsNaN(TestNumbersTestRoundtripF64(math.NaN())) { - panic("roundtrip-f64") - } - - if TestNumbersTestRoundtripChar('a') != 'a' { - panic("roundtrip-char") - } - if TestNumbersTestRoundtripChar(' ') != ' ' { - panic("roundtrip-char") - } - if TestNumbersTestRoundtripChar('🚩') != '🚩' { - panic("roundtrip-char") - } - - TestNumbersTestSetScalar(2) - if TestNumbersTestGetScalar() != 2 { - panic("get-scalar") - } - - TestNumbersTestSetScalar(4) - if TestNumbersTestGetScalar() != 4 { - panic("get-scalar") - } -} - -func (o *NumbersImpl) RoundtripU8(a uint8) uint8 { - return a -} - -func (o *NumbersImpl) RoundtripS8(a int8) int8 { - return a -} - -func (o *NumbersImpl) RoundtripU16(a uint16) uint16 { - return a -} - -func (o *NumbersImpl) RoundtripS16(a int16) int16 { - return a -} - -func (o *NumbersImpl) RoundtripU32(a uint32) uint32 { - return a -} - -func (o *NumbersImpl) RoundtripS32(a int32) int32 { - return a -} - -func (o *NumbersImpl) RoundtripU64(a uint64) uint64 { - return a -} - -func (o *NumbersImpl) RoundtripS64(a int64) int64 { - return a -} - -func (o *NumbersImpl) RoundtripF32(a float32) float32 { - return a -} - -func (o *NumbersImpl) RoundtripF64(a float64) float64 { - return a -} - -func (o *NumbersImpl) RoundtripChar(a rune) rune { - return a -} - -func (o *NumbersImpl) SetScalar(a uint32) { - o.scalar = a -} - -func (o *NumbersImpl) GetScalar() uint32 { - return o.scalar -} - -func assert_eq(a, b interface{}) { - if a != b { - panic("assertion failed") - } -} - -func main() {} diff --git a/tests/runtime/records/wasm.go b/tests/runtime/records/wasm.go deleted file mode 100644 index d7a0d15fb..000000000 --- a/tests/runtime/records/wasm.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - . "wit_records_go/gen" -) - -func init() { - n := &RecordImpl{} - SetRecords(n) - SetExportsTestRecordsTest(n) -} - -type RecordImpl struct{} - -func (r *RecordImpl) TestImports() { - ret := TestRecordsTestMultipleResults() - if ret.F0 != 4 && ret.F1 != 5 { - panic("TestRecordsTestMultipleResults") - } - t := TestRecordsTestSwapTuple(TestRecordsTestTuple2U8U32T{1, 2}) - if t.F0 != 2 && t.F1 != 1 { - panic("TestRecordsTestSwapTuple") - } - - // TODO: how to handle empty flags? - if TestRecordsTestRoundtripFlags1(TestRecordsTestF1_A) != TestRecordsTestF1_A { - panic("TestRecordsTestRoundtripFlags1") - } - if TestRecordsTestRoundtripFlags1(TestRecordsTestF1_B) != TestRecordsTestF1_B { - panic("TestRecordsTestRoundtripFlags1") - } - if TestRecordsTestRoundtripFlags1(TestRecordsTestF1_A|TestRecordsTestF1_B) != TestRecordsTestF1_A|TestRecordsTestF1_B { - panic("TestRecordsTestRoundtripFlags1") - } - - if TestRecordsTestRoundtripFlags2(TestRecordsTestF2_C) != TestRecordsTestF2_C { - panic("TestRecordsTestRoundtripFlags2") - } - if TestRecordsTestRoundtripFlags2(TestRecordsTestF2_D) != TestRecordsTestF2_D { - panic("TestRecordsTestRoundtripFlags2") - } - if TestRecordsTestRoundtripFlags2(TestRecordsTestF2_C|TestRecordsTestF2_E) != TestRecordsTestF2_C|TestRecordsTestF2_E { - panic("TestRecordsTestRoundtripFlags2") - } - - ret2 := TestRecordsTestRoundtripFlags3(TestRecordsTestFlag8_B0, TestRecordsTestFlag16_B1, TestRecordsTestFlag32_B2) - if ret2.F0 != TestRecordsTestFlag8_B0 && ret2.F1 != TestRecordsTestFlag16_B1 && ret2.F2 != TestRecordsTestFlag32_B2 { - panic("TestRecordsTestRoundtripFlags3") - } - - r1 := TestRecordsTestRoundtripRecord1(TestRecordsTestR1{8, TestRecordsTestF1_A}) - if r1.A != 8 && r1.B != TestRecordsTestF1_A { - panic("TestRecordsTestRoundtripRecord1") - } - - r2 := TestRecordsTestRoundtripRecord1(TestRecordsTestR1{0, TestRecordsTestF1_A | TestRecordsTestF1_B}) - if r2.A != 0 && r2.B != TestRecordsTestF1_A|TestRecordsTestF1_B { - panic("TestRecordsTestRoundtripRecord1") - } - - if TestRecordsTestTuple1(TestRecordsTestTuple1U8T{1}).F0 != 1 { - panic("TestRecordsTestTuple1") - } -} - -func (r *RecordImpl) MultipleResults() ExportsTestRecordsTestTuple2U8U16T { - return ExportsTestRecordsTestTuple2U8U16T{100, 200} -} - -func (r *RecordImpl) SwapTuple(a ExportsTestRecordsTestTuple2U8U32T) ExportsTestRecordsTestTuple2U32U8T { - return ExportsTestRecordsTestTuple2U32U8T{a.F1, a.F0} -} - -func (r *RecordImpl) RoundtripFlags1(a ExportsTestRecordsTestF1) ExportsTestRecordsTestF1 { - return a -} - -func (r *RecordImpl) RoundtripFlags2(a ExportsTestRecordsTestF2) ExportsTestRecordsTestF2 { - return a -} - -func (r *RecordImpl) RoundtripFlags3(a ExportsTestRecordsTestFlag8, b ExportsTestRecordsTestFlag16, c ExportsTestRecordsTestFlag32) ExportsTestRecordsTestTuple3Flag8Flag16Flag32T { - return ExportsTestRecordsTestTuple3Flag8Flag16Flag32T{a, b, c} -} - -func (r *RecordImpl) RoundtripRecord1(a ExportsTestRecordsTestR1) ExportsTestRecordsTestR1 { - return a -} - -func (r *RecordImpl) Tuple1(a ExportsTestRecordsTestTuple1U8T) ExportsTestRecordsTestTuple1U8T { - return a -} - -func main() {} diff --git a/tests/runtime/resource_borrow_export/wasm.go b/tests/runtime/resource_borrow_export/wasm.go deleted file mode 100644 index 3430a5f03..000000000 --- a/tests/runtime/resource_borrow_export/wasm.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - . "wit_resource_borrow_export_go/gen" -) - -func init() { - n := &Test{} - SetExportsTestResourceBorrowExportTest(n) -} - -type Test struct{} -type MyThing struct { - val uint32 -} - -func (e Test) ConstructorThing(v uint32) ExportsTestResourceBorrowExportTestThing { - return &MyThing{val: v + 1} -} - -func (e Test) Foo(v ExportsTestResourceBorrowExportTestThing) uint32 { - return v.(*MyThing).val + 2 -} - -func main() {} diff --git a/tests/runtime/resource_borrow_import/wasm.go b/tests/runtime/resource_borrow_import/wasm.go deleted file mode 100644 index 2a9db8c36..000000000 --- a/tests/runtime/resource_borrow_import/wasm.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - . "wit_resource_borrow_import_go/gen" -) - -func init() { - n := &Import{} - SetResourceBorrowImport(n) -} - -type Import struct{} - -func (e Import) Test(v uint32) uint32 { - thing := NewThing(v + 1) - defer thing.Drop() - return TestResourceBorrowImportTestFoo(thing) + 4 - -} - -func main() {} diff --git a/tests/runtime/resource_borrow_simple/wasm.go b/tests/runtime/resource_borrow_simple/wasm.go deleted file mode 100644 index 8685ca4c7..000000000 --- a/tests/runtime/resource_borrow_simple/wasm.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - . "wit_resource_borrow_simple_go/gen" -) - -func init() { - n := &Simple{} - SetResourceBorrowSimple(n) -} - -type Simple struct {} - -func (e Simple) TestImports() { - r := NewR() - ResourceBorrowSimpleTest(r) - r.Drop() -} - -func main() {} \ No newline at end of file diff --git a/tests/runtime/resource_import_and_export/wasm.go b/tests/runtime/resource_import_and_export/wasm.go deleted file mode 100644 index 9ab2b3aa4..000000000 --- a/tests/runtime/resource_import_and_export/wasm.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - . "wit_resource_import_and_export_go/gen" -) - -func init() { - n := &MyTest{} - t := &MyImportExport{} - SetExportsTestResourceImportAndExportTest(t) - SetResourceImportAndExport(n) -} - -type MyTest struct {} - -type MyImportExport struct {} -type MyThing struct { - hostThing TestResourceImportAndExportTestThing -} - -func (t MyImportExport) ConstructorThing(v uint32) ExportsTestResourceImportAndExportTestThing { - thing := &MyThing{ - hostThing: NewThing(v + 1), - } - return thing -} - -func (t MyImportExport) StaticThingBaz(a ExportsTestResourceImportAndExportTestThing, b ExportsTestResourceImportAndExportTestThing) ExportsTestResourceImportAndExportTestThing { - result := StaticThingBaz(a.(*MyThing).hostThing, b.(*MyThing).hostThing).Foo() + 4 - return t.ConstructorThing(result) -} - -func (t *MyThing) MethodThingFoo() uint32 { - return t.hostThing.Foo() + 2 -} - -func (t *MyThing) MethodThingBar(v uint32) { - t.hostThing.Bar(v + 3) -} - -func (e MyTest) ToplevelExport(a ResourceImportAndExportThing) ResourceImportAndExportThing { - return ResourceImportAndExportToplevelImport(a) -} - -func main() {} \ No newline at end of file diff --git a/tests/runtime/resources/wasm.go b/tests/runtime/resources/wasm.go deleted file mode 100644 index 80b5f0462..000000000 --- a/tests/runtime/resources/wasm.go +++ /dev/null @@ -1,100 +0,0 @@ -package main - -import ( - . "wit_resources_go/gen" -) - -func init() { - n := &ExportsImpl{} - SetExports(n) -} - -type ExportsImpl struct { -} - -type MyX struct { - a int32 -} - -type MyZ struct { - a int32 -} - -type MyKebabCase struct { - a uint32 -} - -func (e ExportsImpl) ConstructorX(a int32) ExportsX { - return &MyX{a: a} -} - -func (e ExportsImpl) ConstructorZ(a int32) ExportsZ { - return &MyZ{a: a} -} - -func (e ExportsImpl) ConstructorKebabCase(a uint32) ExportsKebabCase { - return &MyKebabCase{a: a} -} - -func (x *MyX) MethodXGetA() int32 { - return x.a -} - -func (x *MyX) MethodXSetA(a int32) { - x.a = a -} - -func (e ExportsImpl) StaticXAdd(x ExportsX, a int32) ExportsX { - return &MyX{a: x.MethodXGetA() + a} -} - -func (z *MyZ) MethodZGetA() int32 { - return z.a -} - -func (e ExportsImpl) StaticZNumDropped() uint32 { - return 0 -} - -func (e ExportsImpl) Add(z ExportsZ, b ExportsZ) ExportsZ { - return &MyZ{a: z.MethodZGetA() + b.MethodZGetA()} -} - -func (e ExportsImpl) Consume(x ExportsX) { - DropExportsX(x) -} - -func (k *MyKebabCase) MethodKebabCaseGetA() uint32 { - return k.a -} - -func (e ExportsImpl) StaticKebabCaseTakeOwned(k ExportsKebabCase) uint32 { - return k.MethodKebabCaseGetA() -} - -func (e ExportsImpl) TestImports() Result[struct{}, string] { - y := NewY(1) - if y.GetA() != 1 { - panic("y.GetA() != 1") - } - y.SetA(2) - if y.GetA() != 2 { - panic("y.GetA() != 2") - } - - y2 := StaticYAdd(y, 3) - if y2.GetA() != 5 { - panic("y2.GetA() != 5") - } - - y.SetA(5) - - if y.GetA() != 5 { - panic("y.GetA() != 5") - } - - y.Drop() - return Ok[struct{}, string](struct{}{}) -} - -func main() {} diff --git a/tests/runtime/serverless/wasm.go b/tests/runtime/serverless/wasm.go deleted file mode 100644 index 073ecb285..000000000 --- a/tests/runtime/serverless/wasm.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - . "wit_serverless_go/gen" -) - -func init() { - a := HttpImpl{} - SetHandler(a) -} - -type HttpImpl struct { -} - -func (i HttpImpl) HandleHttp(req HandlerRequest) Result[HandlerResponse, HandlerHttpError] { - for _, header := range req.Headers { - println(header.F0) - println(header.F1) - } - for _, arg := range req.Params { - println(arg.F0) - println(arg.F1) - } - response := HandlerResponse{} - response.Status = 200 - response.Body = Some[[]uint8]([]byte("hello world!")) - - var res Result[HandlerResponse, HandlerHttpError] - res.Set(response) - return res -} - -func main() {} diff --git a/tests/runtime/smoke/wasm.go b/tests/runtime/smoke/wasm.go deleted file mode 100644 index ff3c0c316..000000000 --- a/tests/runtime/smoke/wasm.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - . "wit_smoke_go/gen" -) - -func init() { - n := SmokeImpl{} - SetSmoke(n) -} - -type SmokeImpl struct{} - -func (s SmokeImpl) Thunk() { - TestSmokeImportsThunk() -} - -func main() {} From ebe5fc18c1451565ba45562b68d7aeb9bf5a1152 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 12 Mar 2025 13:54:36 -0700 Subject: [PATCH 11/14] [wit-bindgen-rust] Fix 2024 edition compat (#1205) * [wit-bindgen-rust] cargo fix --edition * fix "unsafe op in unsafe fn" 2024 change * fix cargo fmt output * rust-xcrate-test: make edition="2024" to show bindings work in 2024 --------- Co-authored-by: Yosh --- crates/rust/src/bindgen.rs | 119 +++++++++-------- crates/rust/src/interface.rs | 123 +++++++++--------- crates/rust/src/lib.rs | 57 ++++---- .../rust-xcrate-test/Cargo.toml | 2 +- 4 files changed, 159 insertions(+), 142 deletions(-) diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 7ca7094a9..d180e66c9 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -6,7 +6,7 @@ use wit_bindgen_core::abi::{Bindgen, Instruction, LiftLower, WasmType}; use wit_bindgen_core::{dealias, uwrite, uwriteln, wit_parser::*, Source}; pub(super) struct FunctionBindgen<'a, 'b> { - pub gen: &'b mut InterfaceGenerator<'a>, + pub r#gen: &'b mut InterfaceGenerator<'a>, params: Vec, async_: bool, wasm_import_module: &'b str, @@ -28,14 +28,14 @@ pub const POINTER_SIZE_EXPRESSION: &str = "::core::mem::size_of::<*const u8>()"; impl<'a, 'b> FunctionBindgen<'a, 'b> { pub(super) fn new( - gen: &'b mut InterfaceGenerator<'a>, + r#gen: &'b mut InterfaceGenerator<'a>, params: Vec, async_: bool, wasm_import_module: &'b str, always_owned: bool, ) -> FunctionBindgen<'a, 'b> { FunctionBindgen { - gen, + r#gen, params, async_, wasm_import_module, @@ -60,13 +60,13 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } self.emitted_cleanup = true; for (ptr, layout) in mem::take(&mut self.cleanup) { - let alloc = self.gen.path_to_std_alloc_module(); + let alloc = self.r#gen.path_to_std_alloc_module(); self.push_str(&format!( "if {layout}.size() != 0 {{\n{alloc}::dealloc({ptr}.cast(), {layout});\n}}\n" )); } if self.needs_cleanup_list { - let alloc = self.gen.path_to_std_alloc_module(); + let alloc = self.r#gen.path_to_std_alloc_module(); self.push_str(&format!( "for (ptr, layout) in cleanup_list {{\n if layout.size() != 0 {{\n @@ -208,11 +208,11 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { LiftLower::LowerArgsLiftResults => false, LiftLower::LiftArgsLowerResults => true, }; - self.gen.type_path(id, owned) + self.r#gen.type_path(id, owned) } fn typename_lift(&self, id: TypeId) -> String { - self.gen.type_path(id, true) + self.r#gen.type_path(id, true) } fn push_str(&mut self, s: &str) { @@ -226,7 +226,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } fn lift_lower(&self) -> LiftLower { - if self.gen.in_import { + if self.r#gen.in_import { LiftLower::LowerArgsLiftResults } else { LiftLower::LiftArgsLowerResults @@ -285,9 +285,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { // hand out a null pointer. This can happen with async for example // when the params or results are zero-sized. uwrite!(self.src, "let ptr{tmp} = core::ptr::null_mut::();"); - } else if self.gen.in_import { + } else if self.r#gen.in_import { // Import return areas are stored on the stack since this stack - // frame will be around for the entire function call. self.import_return_pointer_area_size = self.import_return_pointer_area_size.max(size); self.import_return_pointer_area_align = self.import_return_pointer_area_align.max(align); @@ -299,8 +298,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { // Export return areas are stored in `static` memory as they need to // persist beyond the function call itself (and are cleaned-up in // `post-return`). - self.gen.return_pointer_area_size = self.gen.return_pointer_area_size.max(size); - self.gen.return_pointer_area_align = self.gen.return_pointer_area_align.max(align); + self.r#gen.return_pointer_area_size = self.r#gen.return_pointer_area_size.max(size); + self.r#gen.return_pointer_area_align = self.r#gen.return_pointer_area_align.max(align); uwriteln!( self.src, "let ptr{tmp} = _RET_AREA.0.as_mut_ptr().cast::();" @@ -310,7 +309,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } fn sizes(&self) -> &SizeAlign { - &self.gen.sizes + &self.r#gen.sizes } fn is_list_canonical(&self, resolve: &Resolve, ty: &Type) -> bool { @@ -321,7 +320,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { // Note that tuples in Rust are not ABI-compatible with component // model tuples, so those are exempted here from canonical lists. Type::Id(id) => { - let info = self.gen.gen.types.get(*id); + let info = self.r#gen.r#gen.types.get(*id); !info.has_resource && !info.has_tuple } _ => true, @@ -363,7 +362,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I64FromU64 | Instruction::I64FromS64 => { let s = operands.pop().unwrap(); - results.push(format!("{}({s})", self.gen.path_to_as_i64())); + results.push(format!("{}({s})", self.r#gen.path_to_as_i64())); } Instruction::I32FromChar | Instruction::I32FromU8 @@ -373,16 +372,16 @@ impl Bindgen for FunctionBindgen<'_, '_> { | Instruction::I32FromU32 | Instruction::I32FromS32 => { let s = operands.pop().unwrap(); - results.push(format!("{}({s})", self.gen.path_to_as_i32())); + results.push(format!("{}({s})", self.r#gen.path_to_as_i32())); } Instruction::CoreF32FromF32 => { let s = operands.pop().unwrap(); - results.push(format!("{}({s})", self.gen.path_to_as_f32())); + results.push(format!("{}({s})", self.r#gen.path_to_as_f32())); } Instruction::CoreF64FromF64 => { let s = operands.pop().unwrap(); - results.push(format!("{}({s})", self.gen.path_to_as_f64())); + results.push(format!("{}({s})", self.r#gen.path_to_as_f64())); } Instruction::F32FromCoreF32 | Instruction::F64FromCoreF64 @@ -399,7 +398,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::CharFromI32 => { results.push(format!( "{}({} as u32)", - self.gen.path_to_char_lift(), + self.r#gen.path_to_char_lift(), operands[0] )); } @@ -412,7 +411,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::BoolFromI32 => { results.push(format!( "{}({} as u8)", - self.gen.path_to_bool_lift(), + self.r#gen.path_to_bool_lift(), operands[0] )); } @@ -426,7 +425,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FlagsLift { flags, ty, .. } => { let repr = RustFlagsRepr::new(flags); - let name = self.gen.type_path(*ty, true); + let name = self.r#gen.type_path(*ty, true); let mut result = format!("{name}::empty()"); for (i, op) in operands.iter().enumerate() { result.push_str(&format!( @@ -464,9 +463,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { let dealiased_resource = dealias(resolve, *resource); let result = if is_own { - let name = self.gen.type_path(dealiased_resource, true); + let name = self.r#gen.type_path(dealiased_resource, true); format!("{name}::from_handle({op} as u32)") - } else if self.gen.is_exported_resource(*resource) { + } else if self.r#gen.is_exported_resource(*resource) { let name = resolve.types[*resource] .name .as_deref() @@ -476,7 +475,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } else { let tmp = format!("handle{}", self.tmp()); self.handle_decls.push(format!("let {tmp};")); - let name = self.gen.type_path(dealiased_resource, true); + let name = self.r#gen.type_path(dealiased_resource, true); format!( "{{\n {tmp} = {name}::from_handle({op} as u32); @@ -493,17 +492,22 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FutureLift { payload, .. } => { - let async_support = self.gen.gen.async_support_path(); + let async_support = self.r#gen.r#gen.async_support_path(); let op = &operands[0]; let name = payload .as_ref() .map(|ty| { - self.gen + self.r#gen .type_name_owned_with_id(ty, Identifier::StreamOrFuturePayload) }) .unwrap_or_else(|| "()".into()); - let ordinal = self.gen.gen.future_payloads.get_index_of(&name).unwrap(); - let path = self.gen.path_to_root(); + let ordinal = self + .r#gen + .r#gen + .future_payloads + .get_index_of(&name) + .unwrap(); + let path = self.r#gen.path_to_root(); results.push(format!( "{async_support}::FutureReader::from_handle_and_vtable\ ({op} as u32, &{path}wit_future::vtable{ordinal}::VTABLE)" @@ -516,17 +520,22 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::StreamLift { payload, .. } => { - let async_support = self.gen.gen.async_support_path(); + let async_support = self.r#gen.r#gen.async_support_path(); let op = &operands[0]; let name = payload .as_ref() .map(|ty| { - self.gen + self.r#gen .type_name_owned_with_id(ty, Identifier::StreamOrFuturePayload) }) .unwrap_or_else(|| "()".into()); - let ordinal = self.gen.gen.stream_payloads.get_index_of(&name).unwrap(); - let path = self.gen.path_to_root(); + let ordinal = self + .r#gen + .r#gen + .stream_payloads + .get_index_of(&name) + .unwrap(); + let path = self.r#gen.path_to_root(); results.push(format!( "{async_support}::StreamReader::from_handle_and_vtable\ ({op} as u32, &{path}wit_stream::vtable{ordinal}::VTABLE)" @@ -539,7 +548,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::ErrorContextLift { .. } => { - let async_support = self.gen.gen.async_support_path(); + let async_support = self.r#gen.r#gen.async_support_path(); let op = &operands[0]; results.push(format!( "{async_support}::ErrorContext::from_handle({op} as u32)" @@ -668,7 +677,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { }} _ => {invalid}(), }}", - invalid = self.gen.path_to_invalid_enum_discriminant(), + invalid = self.r#gen.path_to_invalid_enum_discriminant(), )); } @@ -707,7 +716,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { }} _ => {invalid}(), }}", - invalid = self.gen.path_to_invalid_enum_discriminant(), + invalid = self.r#gen.path_to_invalid_enum_discriminant(), )); } @@ -716,7 +725,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::EnumLift { enum_, ty, .. } => { - let name = self.gen.type_path(*ty, true); + let name = self.r#gen.type_path(*ty, true); let repr = int_repr(enum_.tag()); let op = &operands[0]; let result = format!("{name}::_lift({op} as {repr})"); @@ -747,7 +756,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let tmp = self.tmp(); let len = format!("len{}", tmp); self.push_str(&format!("let {} = {};\n", len, operands[1])); - let vec = self.gen.path_to_vec(); + let vec = self.r#gen.path_to_vec(); let result = format!( "{vec}::from_raw_parts({}.cast(), {1}, {1})", operands[0], len @@ -776,7 +785,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::StringLift => { - let vec = self.gen.path_to_vec(); + let vec = self.r#gen.path_to_vec(); let tmp = self.tmp(); let len = format!("len{}", tmp); uwriteln!(self.src, "let {len} = {};", operands[1]); @@ -785,15 +794,15 @@ impl Bindgen for FunctionBindgen<'_, '_> { "let bytes{tmp} = {vec}::from_raw_parts({}.cast(), {len}, {len});", operands[0], ); - if self.gen.gen.opts.raw_strings { + if self.r#gen.r#gen.opts.raw_strings { results.push(format!("bytes{tmp}")); } else { - results.push(format!("{}(bytes{tmp})", self.gen.path_to_string_lift())); + results.push(format!("{}(bytes{tmp})", self.r#gen.path_to_string_lift())); } } Instruction::ListLower { element, realloc } => { - let alloc = self.gen.path_to_std_alloc_module(); + let alloc = self.r#gen.path_to_std_alloc_module(); let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); let vec = format!("vec{tmp}"); @@ -805,8 +814,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { operand0 = operands[0] )); self.push_str(&format!("let {len} = {vec}.len();\n")); - let size = self.gen.sizes.size(element); - let align = self.gen.sizes.align(element); + let size = self.r#gen.sizes.size(element); + let align = self.r#gen.sizes.align(element); self.push_str(&format!( "let {layout} = {alloc}::Layout::from_size_align_unchecked({vec}.len() * {}, {});\n", size.format(POINTER_SIZE_EXPRESSION), align.format(POINTER_SIZE_EXPRESSION), @@ -840,8 +849,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::ListLift { element, .. } => { let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); - let size = self.gen.sizes.size(element); - let align = self.gen.sizes.align(element); + let size = self.r#gen.sizes.size(element); + let align = self.r#gen.sizes.align(element); let len = format!("len{tmp}"); let base = format!("base{tmp}"); let result = format!("result{tmp}"); @@ -853,7 +862,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { "let {len} = {operand1};\n", operand1 = operands[1] )); - let vec = self.gen.path_to_vec(); + let vec = self.r#gen.path_to_vec(); self.push_str(&format!( "let mut {result} = {vec}::with_capacity({len});\n", )); @@ -868,7 +877,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "{result}.push(e{tmp});"); uwriteln!(self.src, "}}"); results.push(result); - let dealloc = self.gen.path_to_cabi_dealloc(); + let dealloc = self.r#gen.path_to_cabi_dealloc(); self.push_str(&format!( "{dealloc}({base}, {len} * {size}, {align});\n", size = size.format(POINTER_SIZE_EXPRESSION), @@ -897,7 +906,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::AsyncCallWasm { name, .. } => { let func = self.declare_import(name, &[WasmType::Pointer; 2], &[WasmType::I32]); - let async_support = self.gen.gen.async_support_path(); + let async_support = self.r#gen.r#gen.async_support_path(); let operands = operands.join(", "); uwriteln!( self.src, @@ -931,7 +940,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { .unwrap() .to_upper_camel_case(); let call = if self.async_ { - let async_support = self.gen.gen.async_support_path(); + let async_support = self.r#gen.r#gen.async_support_path(); format!("{async_support}::futures::FutureExt::map(T::new") } else { format!("{ty}::new(T::new",) @@ -987,7 +996,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } else { params }; - let async_support = self.gen.gen.async_support_path(); + let async_support = self.r#gen.r#gen.async_support_path(); // TODO: This relies on `abi::Generator` emitting // `AsyncCallReturn` immediately after this instruction to // complete the incomplete expression we generate here. We @@ -1222,7 +1231,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::Malloc { .. } => unimplemented!(), Instruction::GuestDeallocate { size, align } => { - let dealloc = self.gen.path_to_cabi_dealloc(); + let dealloc = self.r#gen.path_to_cabi_dealloc(); self.push_str(&format!( "{dealloc}({op}, {size}, {align});\n", op = operands[0], @@ -1232,7 +1241,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::GuestDeallocateString => { - let dealloc = self.gen.path_to_cabi_dealloc(); + let dealloc = self.r#gen.path_to_cabi_dealloc(); self.push_str(&format!( "{dealloc}({op0}, {op1}, 1);\n", op0 = operands[0], @@ -1262,8 +1271,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::GuestDeallocateList { element } => { let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); - let size = self.gen.sizes.size(element); - let align = self.gen.sizes.align(element); + let size = self.r#gen.sizes.size(element); + let align = self.r#gen.sizes.align(element); let len = format!("len{tmp}"); let base = format!("base{tmp}"); self.push_str(&format!( @@ -1287,7 +1296,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(&body); self.push_str("\n}\n"); } - let dealloc = self.gen.path_to_cabi_dealloc(); + let dealloc = self.r#gen.path_to_cabi_dealloc(); self.push_str(&format!( "{dealloc}({base}, {len} * {size}, {align});\n", size = size.format(POINTER_SIZE_EXPRESSION), diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 4e22346fc..58e47dba9 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -19,7 +19,7 @@ pub struct InterfaceGenerator<'a> { pub(super) identifier: Identifier<'a>, pub in_import: bool, pub sizes: SizeAlign, - pub(super) gen: &'a mut RustWasm, + pub(super) r#gen: &'a mut RustWasm, pub wasm_import_module: &'a str, pub resolve: &'a Resolve, pub return_pointer_area_size: ArchitectureSize, @@ -153,11 +153,11 @@ impl<'i> InterfaceGenerator<'i> { } for func in funcs { - if self.gen.skip.contains(&func.name) { + if self.r#gen.skip.contains(&func.name) { continue; } - let async_ = match &self.gen.opts.async_ { + let async_ = match &self.r#gen.opts.async_ { AsyncConfig::None => false, AsyncConfig::All => true, AsyncConfig::Some { exports, .. } => { @@ -284,7 +284,7 @@ fn _resource_rep(handle: u32) -> *mut u8 ) } }; - let (macro_export, use_vis) = if self.gen.opts.pub_export_macro { + let (macro_export, use_vis) = if self.r#gen.opts.pub_export_macro { ("#[macro_export]", "pub") } else { ("", "pub(crate)") @@ -314,7 +314,7 @@ macro_rules! {macro_name} {{ }; self.generate_raw_cabi_export(func, &ty, "$($path_to_types)*", async_); } - let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); + let export_prefix = self.r#gen.opts.export_prefix.as_deref().unwrap_or(""); for name in resources_to_drop { let module = match self.identifier { Identifier::Interface(_, key) => self.resolve.name_world_key(key), @@ -331,9 +331,11 @@ macro_rules! {macro_name} {{ #[unsafe(export_name = "{export_prefix}{module}#[dtor]{name}")] #[allow(non_snake_case)] unsafe extern "C" fn dtor(rep: *mut u8) {{ - $($path_to_types)*::{camel}::dtor::< - <$ty as $($path_to_types)*::Guest>::{camel} - >(rep) + unsafe {{ + $($path_to_types)*::{camel}::dtor::< + <$ty as $($path_to_types)*::Guest>::{camel} + >(rep) + }} }} }}; "# @@ -460,7 +462,7 @@ macro_rules! {macro_name} {{ let docs = docs.trim_end(); let path_to_root = self.path_to_root(); - let used_static = if self.gen.opts.disable_custom_section_link_helpers { + let used_static = if self.r#gen.opts.disable_custom_section_link_helpers { String::new() } else { format!( @@ -483,9 +485,9 @@ macro_rules! {macro_name} {{ ", ); let map = if self.in_import { - &mut self.gen.import_modules + &mut self.r#gen.import_modules } else { - &mut self.gen.export_modules + &mut self.r#gen.export_modules }; map.push((module, module_path)) } @@ -505,7 +507,7 @@ macro_rules! {macro_name} {{ .unwrap_or_else(|| "$root".into()) ); let func_name = &func.name; - let async_support = self.gen.async_support_path(); + let async_support = self.r#gen.async_support_path(); match &self.resolve.types[ty].kind { TypeDefKind::Future(payload_type) => { @@ -515,8 +517,8 @@ macro_rules! {macro_name} {{ "()".into() }; - if !self.gen.future_payloads.contains_key(&name) { - let ordinal = self.gen.future_payloads.len(); + if !self.r#gen.future_payloads.contains_key(&name) { + let ordinal = self.r#gen.future_payloads.len(); let (size, align) = if let Some(payload_type) = payload_type { ( self.sizes.size(payload_type), @@ -629,7 +631,7 @@ pub mod vtable{ordinal} {{ "#, ); - self.gen.future_payloads.insert(name, code); + self.r#gen.future_payloads.insert(name, code); } } TypeDefKind::Stream(payload_type) => { @@ -639,8 +641,8 @@ pub mod vtable{ordinal} {{ "()".into() }; - if !self.gen.stream_payloads.contains_key(&name) { - let ordinal = self.gen.stream_payloads.len(); + if !self.r#gen.stream_payloads.contains_key(&name) { + let ordinal = self.r#gen.stream_payloads.len(); let (size, align) = if let Some(payload_type) = payload_type { ( self.sizes.size(payload_type), @@ -810,7 +812,7 @@ pub mod vtable{ordinal} {{ "#, ); - self.gen.stream_payloads.insert(name, code); + self.r#gen.stream_payloads.insert(name, code); } } _ => unreachable!(), @@ -821,13 +823,13 @@ pub mod vtable{ordinal} {{ } fn generate_guest_import(&mut self, func: &Function, interface: Option<&WorldKey>) { - if self.gen.skip.contains(&func.name) { + if self.r#gen.skip.contains(&func.name) { return; } self.generate_payloads("", func, interface); - let async_ = match &self.gen.opts.async_ { + let async_ = match &self.r#gen.opts.async_ { AsyncConfig::None => false, AsyncConfig::All => true, AsyncConfig::Some { imports, .. } => imports.contains(&if let Some(key) = interface { @@ -867,13 +869,13 @@ pub mod vtable{ordinal} {{ fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { let mut f = FunctionBindgen::new(self, Vec::new(), true, module, true); - abi::lower_to_memory(f.gen.resolve, &mut f, address.into(), value.into(), ty); + abi::lower_to_memory(f.r#gen.resolve, &mut f, address.into(), value.into(), ty); format!("unsafe {{ {} }}", String::from(f.src)) } fn lift_from_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { let mut f = FunctionBindgen::new(self, Vec::new(), true, module, true); - let result = abi::lift_from_memory(f.gen.resolve, &mut f, address.into(), ty); + let result = abi::lift_from_memory(f.r#gen.resolve, &mut f, address.into(), ty); format!( "let {value} = unsafe {{ {}\n{result} }};", String::from(f.src) @@ -889,7 +891,7 @@ pub mod vtable{ordinal} {{ ) { let mut f = FunctionBindgen::new(self, params, async_, module, false); abi::call( - f.gen.resolve, + f.r#gen.resolve, AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, func, @@ -945,7 +947,7 @@ pub mod vtable{ordinal} {{ let params = self.print_export_sig(func, async_); self.push_str(" {"); - if !self.gen.opts.disable_run_ctors_once_workaround { + if !self.r#gen.opts.disable_run_ctors_once_workaround { let run_ctors_once = self.path_to_run_ctors_once(); // Before executing any other code, use this function to run all // static constructors, if they have not yet been run. This is a @@ -963,7 +965,7 @@ pub mod vtable{ordinal} {{ let mut f = FunctionBindgen::new(self, params, async_, self.wasm_import_module, false); abi::call( - f.gen.resolve, + f.r#gen.resolve, AbiVariant::GuestExport, LiftLower::LiftArgsLowerResults, func, @@ -1005,14 +1007,16 @@ pub mod vtable{ordinal} {{ self.src.push_str("}\n"); if async_ { - let async_support = self.gen.async_support_path(); + let async_support = self.r#gen.async_support_path(); uwrite!( self.src, "\ #[doc(hidden)] #[allow(non_snake_case)] pub unsafe fn __callback_{name_snake}(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i32 {{ - {async_support}::callback(ctx, event0, event1, event2) + unsafe {{ + {async_support}::callback(ctx, event0, event1, event2) + }} }} " ); @@ -1029,7 +1033,7 @@ pub mod vtable{ordinal} {{ self.src.push_str("{\n"); let mut f = FunctionBindgen::new(self, params, async_, self.wasm_import_module, false); - abi::post_return(f.gen.resolve, func, &mut f, async_); + abi::post_return(f.r#gen.resolve, func, &mut f, async_); let FunctionBindgen { needs_cleanup_list, src, @@ -1056,7 +1060,7 @@ pub mod vtable{ordinal} {{ Identifier::World(_) => None, Identifier::StreamOrFuturePayload => unreachable!(), }; - let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); + let export_prefix = self.r#gen.opts.export_prefix.as_deref().unwrap_or(""); let export_name = func.legacy_core_export_name(wasm_module_export_name.as_deref()); let export_name = if async_ { format!("[async-lift]{export_name}") @@ -1075,19 +1079,21 @@ pub mod vtable{ordinal} {{ self.push_str(" {\n"); uwriteln!( self.src, - "{path_to_self}::_export_{name_snake}_cabi::<{ty}>({})", + "unsafe {{ {path_to_self}::_export_{name_snake}_cabi::<{ty}>({}) }}", params.join(", ") ); self.push_str("}\n"); - let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); + let export_prefix = self.r#gen.opts.export_prefix.as_deref().unwrap_or(""); if async_ { uwrite!( self.src, "\ #[unsafe(export_name = \"{export_prefix}[callback]{export_name}\")] unsafe extern \"C\" fn _callback_{name_snake}(ctx: *mut u8, event0: i32, event1: i32, event2: i32) -> i32 {{ - {path_to_self}::__callback_{name_snake}(ctx, event0, event1, event2) + unsafe {{ + {path_to_self}::__callback_{name_snake}(ctx, event0, event1, event2) + }} }} " ); @@ -1103,7 +1109,7 @@ pub mod vtable{ordinal} {{ self.src.push_str("{\n"); uwriteln!( self.src, - "{path_to_self}::__post_return_{name_snake}::<{ty}>({})", + "unsafe {{ {path_to_self}::__post_return_{name_snake}::<{ty}>({}) }}", params.join(", ") ); self.src.push_str("}\n"); @@ -1198,10 +1204,10 @@ pub mod vtable{ordinal} {{ self.src.push_str(extra_trait_items); for func in funcs { - if self.gen.skip.contains(&func.name) { + if self.r#gen.skip.contains(&func.name) { continue; } - let async_ = match &self.gen.opts.async_ { + let async_ = match &self.r#gen.opts.async_ { AsyncConfig::None => false, AsyncConfig::All => true, AsyncConfig::Some { exports, .. } => { @@ -1356,7 +1362,7 @@ pub mod vtable{ordinal} {{ let style = if params_owned { TypeOwnershipStyle::Owned } else { - match self.gen.opts.ownership { + match self.r#gen.opts.ownership { Ownership::Owning => TypeOwnershipStyle::OnlyTopBorrowed, Ownership::Borrowing { .. } => TypeOwnershipStyle::Borrowed, } @@ -1524,7 +1530,7 @@ pub mod vtable{ordinal} {{ // The only possibility at that point is to borrow it at the root // but everything else internally is required to be owned from then // on. - match self.gen.opts.ownership { + match self.r#gen.opts.ownership { Ownership::Owning => Some(lt), Ownership::Borrowing { .. } => { return TypeMode { @@ -1648,7 +1654,7 @@ pub mod vtable{ordinal} {{ match mode.lifetime { Some(lt) => self.print_borrowed_str(lt), None => { - if self.gen.opts.raw_strings { + if self.r#gen.opts.raw_strings { self.push_vec_name(); self.push_str("::"); } else { @@ -1677,7 +1683,8 @@ pub mod vtable{ordinal} {{ pub fn type_path(&self, id: TypeId, owned: bool) -> String { let full_wit_type_name = full_wit_type_name(self.resolve, id); - if let Some(TypeGeneration::Remap(remapped_path)) = self.gen.with.get(&full_wit_type_name) { + if let Some(TypeGeneration::Remap(remapped_path)) = self.r#gen.with.get(&full_wit_type_name) + { remapped_path.clone() } else { self.type_path_with_name( @@ -1788,7 +1795,7 @@ pub mod vtable{ordinal} {{ fn modes_of(&self, ty: TypeId) -> Vec<(String, TypeMode)> { let info = self.info(ty); let mut result = Vec::new(); - if !self.gen.opts.generate_unused_types { + if !self.r#gen.opts.generate_unused_types { // If this type isn't actually used, no need to generate it. if !info.owned && !info.borrowed { return result; @@ -1808,7 +1815,7 @@ pub mod vtable{ordinal} {{ } else if a == b { // If the modes are the same then there's only one result. result.push((self.result_name(ty), a)); - } else if info.owned || matches!(self.gen.opts.ownership, Ownership::Owning) { + } else if info.owned || matches!(self.r#gen.opts.ownership, Ownership::Owning) { // If this type is owned or if ownership is preferred then the owned // variant is used as a priority. This is where the generator's // configuration comes into play. @@ -1825,7 +1832,7 @@ pub mod vtable{ordinal} {{ let info = self.info(id); // We use a BTree set to make sure we don't have any duplicates and we have a stable order let additional_derives: BTreeSet = self - .gen + .r#gen .opts .additional_derive_attributes .iter() @@ -1901,7 +1908,7 @@ pub mod vtable{ordinal} {{ self.push_str("write!(f, \"{:?}\", self)\n"); self.push_str("}\n"); self.push_str("}\n"); - if self.gen.opts.std_feature { + if self.r#gen.opts.std_feature { self.push_str("#[cfg(feature = \"std\")]\n"); } self.push_str("impl std::error::Error for "); @@ -1936,7 +1943,7 @@ pub mod vtable{ordinal} {{ let info = self.info(id); // We use a BTree set to make sure we don't have any duplicates and have a stable order let additional_derives: BTreeSet = self - .gen + .r#gen .opts .additional_derive_attributes .iter() @@ -2003,7 +2010,7 @@ pub mod vtable{ordinal} {{ self.push_str("}\n"); self.push_str("\n"); - if self.gen.opts.std_feature { + if self.r#gen.opts.std_feature { self.push_str("#[cfg(feature = \"std\")]\n"); } self.push_str("impl"); @@ -2102,7 +2109,7 @@ pub mod vtable{ordinal} {{ // We use a BTree set to make sure we don't have any duplicates and a stable order let mut derives: BTreeSet = BTreeSet::new(); if !self - .gen + .r#gen .opts .additional_derive_ignore .contains(&name.to_kebab_case()) @@ -2187,7 +2194,7 @@ pub mod vtable{ordinal} {{ self.push_str("}\n"); self.push_str("}\n"); self.push_str("\n"); - if self.gen.opts.std_feature { + if self.r#gen.opts.std_feature { self.push_str("#[cfg(feature = \"std\")]\n"); } self.push_str("impl std::error::Error for "); @@ -2250,7 +2257,7 @@ pub mod vtable{ordinal} {{ fn uses_two_names(&self, info: &TypeInfo) -> bool { // Types are only duplicated if explicitly requested ... matches!( - self.gen.opts.ownership, + self.r#gen.opts.ownership, Ownership::Borrowing { duplicate_if_necessary: true } @@ -2266,7 +2273,7 @@ pub mod vtable{ordinal} {{ } fn path_to_interface(&self, interface: InterfaceId) -> Option { - let InterfaceName { path, remapped } = &self.gen.interface_names[&interface]; + let InterfaceName { path, remapped } = &self.r#gen.interface_names[&interface]; if *remapped { let mut path_to_root = self.path_to_root(); path_to_root.push_str(path); @@ -2319,7 +2326,7 @@ pub mod vtable{ordinal} {{ // Interfaces are "stateful" currently where whatever we last saw // them as dictates whether it's exported or not. - TypeOwner::Interface(i) => !self.gen.interface_last_seen_as_import[&i], + TypeOwner::Interface(i) => !self.r#gen.interface_last_seen_as_import[&i], // Shouldn't be the case for resources TypeOwner::None => unreachable!(), @@ -2336,7 +2343,7 @@ pub mod vtable{ordinal} {{ } fn info(&self, ty: TypeId) -> TypeInfo { - self.gen.types.get(ty) + self.r#gen.types.get(ty) } fn print_borrowed_str(&mut self, lifetime: &'static str) { @@ -2345,7 +2352,7 @@ pub mod vtable{ordinal} {{ self.push_str(lifetime); self.push_str(" "); } - if self.gen.opts.raw_strings { + if self.r#gen.opts.raw_strings { self.push_str("[u8]"); } else { self.push_str("str"); @@ -2425,7 +2432,7 @@ pub mod vtable{ordinal} {{ name_in_runtime_module: &str, ) -> String { self.needs_runtime_module = true; - self.gen.rt_module.insert(item); + self.r#gen.rt_module.insert(item); let prefix = if let Identifier::StreamOrFuturePayload = &self.identifier { "super::super::" } else { @@ -2661,7 +2668,7 @@ impl<'a> {camel}Borrow<'a>{{ fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) { self.src.push_str(&format!( "{bitflags}::bitflags! {{\n", - bitflags = self.gen.bitflags_path() + bitflags = self.r#gen.bitflags_path() )); self.rustdoc(docs); let repr = RustFlagsRepr::new(flags); @@ -2739,7 +2746,7 @@ impl<'a> {camel}Borrow<'a>{{ } fn type_future(&mut self, _id: TypeId, name: &str, ty: &Option, docs: &Docs) { - let async_support = self.gen.async_support_path(); + let async_support = self.r#gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2756,7 +2763,7 @@ impl<'a> {camel}Borrow<'a>{{ } fn type_stream(&mut self, _id: TypeId, name: &str, ty: &Option, docs: &Docs) { - let async_support = self.gen.async_support_path(); + let async_support = self.r#gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2861,7 +2868,7 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< } fn anonymous_type_future(&mut self, _id: TypeId, ty: &Option, _docs: &Docs) { - let async_support = self.interface.gen.async_support_path(); + let async_support = self.interface.r#gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, @@ -2874,7 +2881,7 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< } fn anonymous_type_stream(&mut self, _id: TypeId, ty: &Option, _docs: &Docs) { - let async_support = self.interface.gen.async_support_path(); + let async_support = self.interface.r#gen.async_support_path(); let mode = TypeMode { style: TypeOwnershipStyle::Owned, lists_borrowed: false, diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index f72f76f83..cfaebda24 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -347,7 +347,7 @@ impl RustWasm { wasm_import_module, src: Source::default(), in_import, - gen: self, + r#gen: self, sizes, resolve, return_pointer_area_size: Default::default(), @@ -1128,26 +1128,26 @@ impl WorldGenerator for RustWasm { self.interface_last_seen_as_import.insert(id, true); let wasm_import_module = resolve.name_world_key(name); - let mut gen = self.interface( + let mut r#gen = self.interface( Identifier::Interface(id, name), &wasm_import_module, resolve, true, ); - let (snake, module_path) = gen.start_append_submodule(name); - if gen.gen.name_interface(resolve, id, name, false)? { + let (snake, module_path) = r#gen.start_append_submodule(name); + if r#gen.r#gen.name_interface(resolve, id, name, false)? { return Ok(()); } for (name, ty_id) in to_define { - gen.define_type(&name, *ty_id); + r#gen.define_type(&name, *ty_id); } - gen.generate_imports(resolve.interfaces[id].functions.values(), Some(name)); + r#gen.generate_imports(resolve.interfaces[id].functions.values(), Some(name)); let docs = &resolve.interfaces[id].docs; - gen.finish_append_submodule(&snake, module_path, docs); + r#gen.finish_append_submodule(&snake, module_path, docs); Ok(()) } @@ -1161,11 +1161,11 @@ impl WorldGenerator for RustWasm { ) { self.import_funcs_called = true; - let mut gen = self.interface(Identifier::World(world), "$root", resolve, true); + let mut r#gen = self.interface(Identifier::World(world), "$root", resolve, true); - gen.generate_imports(funcs.iter().map(|(_, func)| *func), None); + r#gen.generate_imports(funcs.iter().map(|(_, func)| *func), None); - let src = gen.finish(); + let src = r#gen.finish(); self.src.push_str(&src); } @@ -1192,40 +1192,40 @@ impl WorldGenerator for RustWasm { self.interface_last_seen_as_import.insert(id, false); let wasm_import_module = format!("[export]{}", resolve.name_world_key(name)); - let mut gen = self.interface( + let mut r#gen = self.interface( Identifier::Interface(id, name), &wasm_import_module, resolve, false, ); - let (snake, module_path) = gen.start_append_submodule(name); - if gen.gen.name_interface(resolve, id, name, true)? { + let (snake, module_path) = r#gen.start_append_submodule(name); + if r#gen.r#gen.name_interface(resolve, id, name, true)? { return Ok(()); } for (name, ty_id) in to_define { - gen.define_type(&name, *ty_id); + r#gen.define_type(&name, *ty_id); } let macro_name = - gen.generate_exports(Some((id, name)), resolve.interfaces[id].functions.values())?; + r#gen.generate_exports(Some((id, name)), resolve.interfaces[id].functions.values())?; let docs = &resolve.interfaces[id].docs; - gen.finish_append_submodule(&snake, module_path, docs); + r#gen.finish_append_submodule(&snake, module_path, docs); self.export_macros .push((macro_name, self.interface_names[&id].path.clone())); if self.opts.stubs { let world_id = self.world.unwrap(); - let mut gen = self.interface( + let mut r#gen = self.interface( Identifier::World(world_id), &wasm_import_module, resolve, false, ); - gen.generate_stub(Some((id, name)), resolve.interfaces[id].functions.values()); - let stub = gen.finish(); + r#gen.generate_stub(Some((id, name)), resolve.interfaces[id].functions.values()); + let stub = r#gen.finish(); self.src.push_str(&stub); } Ok(()) @@ -1238,16 +1238,17 @@ impl WorldGenerator for RustWasm { funcs: &[(&str, &Function)], _files: &mut Files, ) -> Result<()> { - let mut gen = self.interface(Identifier::World(world), "[export]$root", resolve, false); - let macro_name = gen.generate_exports(None, funcs.iter().map(|f| f.1))?; - let src = gen.finish(); + let mut r#gen = self.interface(Identifier::World(world), "[export]$root", resolve, false); + let macro_name = r#gen.generate_exports(None, funcs.iter().map(|f| f.1))?; + let src = r#gen.finish(); self.src.push_str(&src); self.export_macros.push((macro_name, String::new())); if self.opts.stubs { - let mut gen = self.interface(Identifier::World(world), "[export]$root", resolve, false); - gen.generate_stub(None, funcs.iter().map(|f| f.1)); - let stub = gen.finish(); + let mut r#gen = + self.interface(Identifier::World(world), "[export]$root", resolve, false); + r#gen.generate_stub(None, funcs.iter().map(|f| f.1)); + let stub = r#gen.finish(); self.src.push_str(&stub); } Ok(()) @@ -1273,11 +1274,11 @@ impl WorldGenerator for RustWasm { } self.generated_types.insert(full_name); } - let mut gen = self.interface(Identifier::World(world), "$root", resolve, true); + let mut r#gen = self.interface(Identifier::World(world), "$root", resolve, true); for (name, ty) in to_define { - gen.define_type(name, *ty); + r#gen.define_type(name, *ty); } - let src = gen.finish(); + let src = r#gen.finish(); self.src.push_str(&src); } diff --git a/crates/test-rust-wasm/rust-xcrate-test/Cargo.toml b/crates/test-rust-wasm/rust-xcrate-test/Cargo.toml index 148687db5..0e3825024 100644 --- a/crates/test-rust-wasm/rust-xcrate-test/Cargo.toml +++ b/crates/test-rust-wasm/rust-xcrate-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-xcrate-test" -edition.workspace = true +edition = "2024" publish = false [dependencies] From e7656df942df8754fdd3cecf5e043db591560e12 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 12 Mar 2025 17:02:00 -0500 Subject: [PATCH 12/14] Add a `wit-bindgen test` subcommand (#1192) * Add a `wit-bindgen test` subcommand As I've read more and more of the Rust async runtime support and other various bits and pieces I've wanted more and more the ability to easily write tests for guest interactions with the host. While I don't think it's feasible to generate arbitrary hosts I do think it's possible to do this much more easily than is done today with the testing support in this repository. In essence this commit is an implementation of #1161. The goal of this commit is to add a `wit-bindgen test` test suite runner. This test suite will be used to migrate all existing tests in this repository to this new framework. In the limit this is expected to make it easier to write tests (no Rust knowledge necessary), make it more flexible to write tests (now you can use raw `*.wat`), and additionally improve the quality of the test suite by making it more reusable. The reusability isn't the highest priority at this time as it's not clear what else would want to reuse this, but my hope is that this refactoring is at least a large-ish leap forward towards having a component model test suite of some kind eventually. * Add the p2 target * Add new crate to publish list * Update crate metadata * Add back in crate test * Don't prepare ignored languages * Actually fail the process if tests fail * Install wasmtime on CI too * Always install wasi-sdk for all deps (which is now just a bunch of deps too) * Migrate some more Rust codegen tests to the new framework * Improve rendering of errors in test subcommand Use one deduplicated method for showing a list of errors. * Remove warnings in generated Rust programs * More migrated Rust codegen tests Add in configuration to drop default bindings args, aka `--generate-all` in Rust. * Move Rust/C-specific tests to their own folder * Migrate another rust codegen test * Migrate Rust numbers test to new framework * Migrate numbers/wasm.c to new framework * Migrate the `wasm/lists.rs` test to the new framework * Migrate `lists/wasm.c` test to new framework * Update test docs * Beef up Rust support to pull in `futures` and support WASIp1 * The `futures` crate will be necessary for upcoming async tests. * Support for WASIp1 is required as `wasm32-wasip2` can't produce an async component natively as it's not new enough. * Test runner support is extended slightly to support passing runtime arguments, such as `-Wcomponent-model-async`. * Document test's `config.rs` * Enable debuginfo in Rust tests Helps symbolicate backtraces better. * Fixup some merge conflicts --- .github/actions/install-wasi-sdk/action.yml | 5 +- .github/workflows/main.yml | 11 +- Cargo.lock | 377 ++++-- Cargo.toml | 6 +- ci/publish.rs | 1 + crates/c/Cargo.toml | 4 - crates/c/src/lib.rs | 2 +- crates/c/tests/codegen.rs | 156 --- crates/guest-rust/rt/build.rs | 2 +- crates/rust/src/bindgen.rs | 2 +- crates/rust/src/interface.rs | 2 +- crates/rust/src/lib.rs | 6 +- crates/rust/tests/codegen.rs | 790 ------------- crates/rust/tests/codegen_no_std.rs | 119 -- crates/test-rust-wasm/Cargo.toml | 8 - crates/test-rust-wasm/src/bin/lists.rs | 3 - crates/test-rust-wasm/src/bin/numbers.rs | 3 - crates/test/Cargo.toml | 32 + crates/test/README.md | 238 ++++ crates/test/src/c.rs | 187 +++ crates/test/src/config.rs | 117 ++ crates/test/src/custom.rs | 156 +++ crates/test/src/lib.rs | 1052 +++++++++++++++++ crates/test/src/runner.rs | 50 + crates/test/src/rust.rs | 252 ++++ crates/test/src/wat.rs | 49 + src/bin/wit-bindgen.rs | 7 + tests/codegen/error-context.wit | 2 + tests/codegen/futures.wit | 2 + tests/codegen/resources-with-futures.wit | 2 + tests/codegen/resources-with-streams.wit | 2 + tests/codegen/streams.wit | 2 + tests/runtime-new/c/rename/runner.c | 9 + tests/runtime-new/c/rename/test.c | 7 + tests/runtime-new/c/rename/test.wit | 19 + tests/runtime-new/demo/runner-component.wat | 23 + tests/runtime-new/demo/runner-core.wat | 9 + tests/runtime-new/demo/runner.c | 5 + tests/runtime-new/demo/runner.cpp | 5 + tests/runtime-new/demo/runner.rs | 5 + tests/runtime-new/demo/runner2.rs | 5 + tests/runtime-new/demo/test-component.wat | 10 + tests/runtime-new/demo/test-core.wat | 5 + tests/runtime-new/demo/test.c | 4 + tests/runtime-new/demo/test.cpp | 4 + tests/runtime-new/demo/test.rs | 9 + tests/runtime-new/demo/test.wit | 13 + tests/runtime-new/demo/test2.rs | 9 + tests/runtime-new/gated-features/runner.rs | 10 + tests/runtime-new/gated-features/test.rs | 14 + tests/runtime-new/gated-features/test.wit | 17 + tests/runtime-new/lists-alias/runner.rs | 10 + tests/runtime-new/lists-alias/test.rs | 15 + tests/runtime-new/lists-alias/test.wit | 17 + tests/runtime-new/lists/alloc.rs | 30 + tests/runtime-new/lists/runner.c | 235 ++++ .../wasm.rs => runtime-new/lists/runner.rs} | 196 ++- tests/runtime-new/lists/test.c | 163 +++ tests/runtime-new/lists/test.rs | 99 ++ tests/runtime-new/lists/test.wit | 39 + tests/runtime-new/numbers/runner.c | 59 + tests/runtime-new/numbers/runner.rs | 55 + tests/runtime-new/numbers/test.c | 58 + tests/runtime-new/numbers/test.rs | 63 + tests/runtime-new/numbers/test.wit | 26 + .../package-with-version/runner.rs | 7 + .../runtime-new/package-with-version/test.rs | 17 + .../runtime-new/package-with-version/test.wit | 14 + .../rust/alternative-bitflags/runner.rs | 11 + .../rust/alternative-bitflags/test.rs | 17 + .../rust/alternative-bitflags/test.wit | 19 + .../runtime-new/rust/custom-derives/runner.rs | 10 + tests/runtime-new/rust/custom-derives/test.rs | 43 + .../runtime-new/rust/custom-derives/test.wit | 31 + .../runner.rs | 9 + .../test.rs | 11 + .../test.wit | 12 + .../rust/owned-resource-deref-mut/runner.rs | 9 + .../rust/owned-resource-deref-mut/test.rs | 33 + .../rust/owned-resource-deref-mut/test.wit | 16 + .../rust/raw-strings/runner-nostd.rs | 18 + .../rust/raw-strings/runner-std.rs | 12 + tests/runtime-new/rust/raw-strings/test.rs | 17 + tests/runtime-new/rust/raw-strings/test.wit | 15 + .../rust/run-ctors-once-workaround/runner.rs | 7 + .../rust/run-ctors-once-workaround/test.rs | 11 + .../rust/run-ctors-once-workaround/test.wit | 13 + tests/runtime-new/rust/skip/runner.rs | 6 + tests/runtime-new/rust/skip/test-nostd.rs | 16 + tests/runtime-new/rust/skip/test-std.rs | 14 + tests/runtime-new/rust/skip/test.wit | 15 + .../rust/with-and-resources/runner.rs | 26 + .../rust/with-and-resources/test.rs | 26 + .../rust/with-and-resources/test.wit | 22 + .../runner-generate-all.rs | 9 + .../runner-generate-one.rs | 9 + .../rust/with-option-generate/test.rs | 13 + .../rust/with-option-generate/test.wit | 19 + tests/runtime-new/rust/with-types/runner.rs | 68 ++ tests/runtime-new/rust/with-types/test.rs | 55 + tests/runtime-new/rust/with-types/test.wit | 76 ++ tests/runtime-new/rust/with/runner.rs | 29 + tests/runtime-new/rust/with/test.rs | 13 + tests/runtime-new/rust/with/test.wit | 21 + tests/runtime-new/strings-alias/runner.rs | 10 + tests/runtime-new/strings-alias/test.rs | 15 + tests/runtime-new/strings-alias/test.wit | 17 + .../strings-simple/runner-nostd.rs | 18 + .../runtime-new/strings-simple/runner-std.rs | 10 + tests/runtime-new/strings-simple/test.rs | 15 + tests/runtime-new/strings-simple/test.wit | 15 + tests/runtime-new/symbol-conflicts/runner.rs | 8 + tests/runtime-new/symbol-conflicts/test.rs | 25 + tests/runtime-new/symbol-conflicts/test.wit | 31 + tests/runtime-new/unused-types/runner.rs | 14 + tests/runtime-new/unused-types/test.rs | 19 + tests/runtime-new/unused-types/test.wit | 23 + tests/runtime/lists/wasm.c | 382 ------ tests/runtime/numbers/wasm.c | 112 -- tests/runtime/numbers/wasm.rs | 121 -- 120 files changed, 4608 insertions(+), 1910 deletions(-) delete mode 100644 crates/c/tests/codegen.rs delete mode 100644 crates/rust/tests/codegen_no_std.rs delete mode 100644 crates/test-rust-wasm/src/bin/lists.rs delete mode 100644 crates/test-rust-wasm/src/bin/numbers.rs create mode 100644 crates/test/Cargo.toml create mode 100644 crates/test/README.md create mode 100644 crates/test/src/c.rs create mode 100644 crates/test/src/config.rs create mode 100644 crates/test/src/custom.rs create mode 100644 crates/test/src/lib.rs create mode 100644 crates/test/src/runner.rs create mode 100644 crates/test/src/rust.rs create mode 100644 crates/test/src/wat.rs create mode 100644 tests/runtime-new/c/rename/runner.c create mode 100644 tests/runtime-new/c/rename/test.c create mode 100644 tests/runtime-new/c/rename/test.wit create mode 100644 tests/runtime-new/demo/runner-component.wat create mode 100644 tests/runtime-new/demo/runner-core.wat create mode 100644 tests/runtime-new/demo/runner.c create mode 100644 tests/runtime-new/demo/runner.cpp create mode 100644 tests/runtime-new/demo/runner.rs create mode 100644 tests/runtime-new/demo/runner2.rs create mode 100644 tests/runtime-new/demo/test-component.wat create mode 100644 tests/runtime-new/demo/test-core.wat create mode 100644 tests/runtime-new/demo/test.c create mode 100644 tests/runtime-new/demo/test.cpp create mode 100644 tests/runtime-new/demo/test.rs create mode 100644 tests/runtime-new/demo/test.wit create mode 100644 tests/runtime-new/demo/test2.rs create mode 100644 tests/runtime-new/gated-features/runner.rs create mode 100644 tests/runtime-new/gated-features/test.rs create mode 100644 tests/runtime-new/gated-features/test.wit create mode 100644 tests/runtime-new/lists-alias/runner.rs create mode 100644 tests/runtime-new/lists-alias/test.rs create mode 100644 tests/runtime-new/lists-alias/test.wit create mode 100644 tests/runtime-new/lists/alloc.rs create mode 100644 tests/runtime-new/lists/runner.c rename tests/{runtime/lists/wasm.rs => runtime-new/lists/runner.rs} (52%) create mode 100644 tests/runtime-new/lists/test.c create mode 100644 tests/runtime-new/lists/test.rs create mode 100644 tests/runtime-new/lists/test.wit create mode 100644 tests/runtime-new/numbers/runner.c create mode 100644 tests/runtime-new/numbers/runner.rs create mode 100644 tests/runtime-new/numbers/test.c create mode 100644 tests/runtime-new/numbers/test.rs create mode 100644 tests/runtime-new/numbers/test.wit create mode 100644 tests/runtime-new/package-with-version/runner.rs create mode 100644 tests/runtime-new/package-with-version/test.rs create mode 100644 tests/runtime-new/package-with-version/test.wit create mode 100644 tests/runtime-new/rust/alternative-bitflags/runner.rs create mode 100644 tests/runtime-new/rust/alternative-bitflags/test.rs create mode 100644 tests/runtime-new/rust/alternative-bitflags/test.wit create mode 100644 tests/runtime-new/rust/custom-derives/runner.rs create mode 100644 tests/runtime-new/rust/custom-derives/test.rs create mode 100644 tests/runtime-new/rust/custom-derives/test.wit create mode 100644 tests/runtime-new/rust/disable-custom-section-link-helpers/runner.rs create mode 100644 tests/runtime-new/rust/disable-custom-section-link-helpers/test.rs create mode 100644 tests/runtime-new/rust/disable-custom-section-link-helpers/test.wit create mode 100644 tests/runtime-new/rust/owned-resource-deref-mut/runner.rs create mode 100644 tests/runtime-new/rust/owned-resource-deref-mut/test.rs create mode 100644 tests/runtime-new/rust/owned-resource-deref-mut/test.wit create mode 100644 tests/runtime-new/rust/raw-strings/runner-nostd.rs create mode 100644 tests/runtime-new/rust/raw-strings/runner-std.rs create mode 100644 tests/runtime-new/rust/raw-strings/test.rs create mode 100644 tests/runtime-new/rust/raw-strings/test.wit create mode 100644 tests/runtime-new/rust/run-ctors-once-workaround/runner.rs create mode 100644 tests/runtime-new/rust/run-ctors-once-workaround/test.rs create mode 100644 tests/runtime-new/rust/run-ctors-once-workaround/test.wit create mode 100644 tests/runtime-new/rust/skip/runner.rs create mode 100644 tests/runtime-new/rust/skip/test-nostd.rs create mode 100644 tests/runtime-new/rust/skip/test-std.rs create mode 100644 tests/runtime-new/rust/skip/test.wit create mode 100644 tests/runtime-new/rust/with-and-resources/runner.rs create mode 100644 tests/runtime-new/rust/with-and-resources/test.rs create mode 100644 tests/runtime-new/rust/with-and-resources/test.wit create mode 100644 tests/runtime-new/rust/with-option-generate/runner-generate-all.rs create mode 100644 tests/runtime-new/rust/with-option-generate/runner-generate-one.rs create mode 100644 tests/runtime-new/rust/with-option-generate/test.rs create mode 100644 tests/runtime-new/rust/with-option-generate/test.wit create mode 100644 tests/runtime-new/rust/with-types/runner.rs create mode 100644 tests/runtime-new/rust/with-types/test.rs create mode 100644 tests/runtime-new/rust/with-types/test.wit create mode 100644 tests/runtime-new/rust/with/runner.rs create mode 100644 tests/runtime-new/rust/with/test.rs create mode 100644 tests/runtime-new/rust/with/test.wit create mode 100644 tests/runtime-new/strings-alias/runner.rs create mode 100644 tests/runtime-new/strings-alias/test.rs create mode 100644 tests/runtime-new/strings-alias/test.wit create mode 100644 tests/runtime-new/strings-simple/runner-nostd.rs create mode 100644 tests/runtime-new/strings-simple/runner-std.rs create mode 100644 tests/runtime-new/strings-simple/test.rs create mode 100644 tests/runtime-new/strings-simple/test.wit create mode 100644 tests/runtime-new/symbol-conflicts/runner.rs create mode 100644 tests/runtime-new/symbol-conflicts/test.rs create mode 100644 tests/runtime-new/symbol-conflicts/test.wit create mode 100644 tests/runtime-new/unused-types/runner.rs create mode 100644 tests/runtime-new/unused-types/test.rs create mode 100644 tests/runtime-new/unused-types/test.wit delete mode 100644 tests/runtime/lists/wasm.c delete mode 100644 tests/runtime/numbers/wasm.c delete mode 100644 tests/runtime/numbers/wasm.rs diff --git a/.github/actions/install-wasi-sdk/action.yml b/.github/actions/install-wasi-sdk/action.yml index 741e51656..468e3873a 100644 --- a/.github/actions/install-wasi-sdk/action.yml +++ b/.github/actions/install-wasi-sdk/action.yml @@ -23,4 +23,7 @@ runs: uses: bytecodealliance/actions/wasm-tools/setup@v1 with: version: "1.215.0" - github_token: ${{ github.token }} + - name: Setup `wasmtime` + uses: bytecodealliance/actions/wasmtime/setup@v1 + with: + version: "30.0.1" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index de6f0bb49..7a751671b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,13 +71,12 @@ jobs: submodules: true - name: Install Rust run: rustup update stable --no-self-update && rustup default stable - - run: rustup target add wasm32-wasip1 + - run: rustup target add wasm32-wasip1 wasm32-wasip2 - run: rustup target add wasm32-unknown-unknown if: matrix.lang == 'rust' - uses: ./.github/actions/install-wasi-sdk - if: matrix.lang == 'c' || matrix.lang == 'csharp' - name: Setup .NET uses: actions/setup-dotnet@v4 @@ -106,6 +105,14 @@ jobs: --no-default-features \ --features ${{ matrix.lang }} \ --release + - run: | + cargo run test --languages rust,${{ matrix.lang }} tests/codegen \ + --artifacts target/artifacts \ + --rust-wit-bindgen-path ./crates/guest-rust + - run: | + cargo run test --languages rust,${{ matrix.lang }} tests/runtime-new \ + --artifacts target/artifacts \ + --rust-wit-bindgen-path ./crates/guest-rust test_unit: name: Crate Unit Tests diff --git a/Cargo.lock b/Cargo.lock index 8c1e39301..42cb6a723 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "ambient-authority" version = "0.0.2" @@ -105,9 +114,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "arbitrary" @@ -117,9 +126,9 @@ checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", @@ -167,9 +176,18 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bitmaps" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] [[package]] name = "block-buffer" @@ -194,9 +212,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cap-fs-ext" @@ -277,9 +295,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.14" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "jobserver", "libc", @@ -294,9 +312,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.30" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", "clap_derive", @@ -304,14 +322,15 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.30" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", + "terminal_size", ] [[package]] @@ -615,9 +634,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embedded-io" @@ -664,15 +683,21 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fd-lock" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" +checksum = "c44818c96aec5cadc9dacfb97bbcbcfc19a0de75b218412d56f57fbaab94e439" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.0" @@ -1058,6 +1083,20 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indexmap" version = "2.7.1" @@ -1108,9 +1147,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "ittapi" @@ -1165,9 +1204,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libm" @@ -1193,15 +1232,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "mach2" @@ -1235,9 +1274,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -1283,6 +1322,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1297,9 +1346,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "postcard" @@ -1324,9 +1373,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ "proc-macro2", "syn", @@ -1334,9 +1383,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -1363,9 +1412,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] @@ -1400,6 +1449,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1444,6 +1502,35 @@ dependencies = [ "smallvec", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rust-xcrate-test" version = "0.0.0" @@ -1480,39 +1567,39 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -1521,9 +1608,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1540,6 +1627,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1566,6 +1666,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.9" @@ -1629,9 +1739,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", @@ -1680,6 +1790,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "test-artifacts" version = "0.0.0" @@ -1689,10 +1809,10 @@ name = "test-helpers" version = "0.0.0" dependencies = [ "codegen-macro", - "wasm-encoder 0.227.0", + "wasm-encoder 0.227.1", "wit-bindgen-core", "wit-component", - "wit-parser 0.227.0", + "wit-parser 0.227.1", ] [[package]] @@ -1833,9 +1953,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" @@ -1849,6 +1969,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "url" version = "2.5.4" @@ -1880,9 +2006,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" [[package]] name = "version_check" @@ -1896,6 +2022,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi-preview1-component-adapter-provider" +version = "30.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddbd7f2a9e3635abe5d4df93b12cadc8d6818079785ee4fab3719ae3c85a064e" + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1954,6 +2086,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-compose" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606f006290e2964ba8075a8dd079358ede1e588971cb761720db43b3824f26e5" +dependencies = [ + "anyhow", + "heck 0.4.1", + "im-rc", + "indexmap", + "log", + "petgraph", + "serde", + "serde_derive", + "serde_yaml", + "smallvec", + "wasm-encoder 0.227.1", + "wasmparser 0.227.1", + "wat", +] + [[package]] name = "wasm-encoder" version = "0.217.1" @@ -1965,19 +2118,19 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.227.0" +version = "0.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829806010c17fa417fa56a42b76efadb35d70dbd972de9f07373b87d2729b698" +checksum = "80bb72f02e7fbf07183443b27b0f3d4144abf8c114189f2e088ed95b696a7822" dependencies = [ "leb128fmt", - "wasmparser 0.227.0", + "wasmparser 0.227.1", ] [[package]] name = "wasm-metadata" -version = "0.227.0" +version = "0.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220471107952f7a42f71d95627deede9a4183e6c7744ad189d4f8c383f397689" +checksum = "ce1ef0faabbbba6674e97a56bee857ccddf942785a336c8b47b42373c922a91d" dependencies = [ "anyhow", "auditable-serde", @@ -1988,8 +2141,8 @@ dependencies = [ "serde_json", "spdx", "url", - "wasm-encoder 0.227.0", - "wasmparser 0.227.0", + "wasm-encoder 0.227.1", + "wasmparser 0.227.1", ] [[package]] @@ -2008,9 +2161,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.227.0" +version = "0.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15e32b1ab55e5e112f7c1dabd0d3f57c61992bfb0c4d4a6f141fe65c10ba750" +checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" dependencies = [ "bitflags", "hashbrown 0.15.2", @@ -2329,24 +2482,24 @@ dependencies = [ [[package]] name = "wast" -version = "227.0.0" +version = "227.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9ba0f7fe54ce2895314110aac579043d43175f66201153a0549badfa0fb04e" +checksum = "85c14e5042b16c9d267da3b9b0f4529870455178415286312c25c34dfc1b2816" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.227.0", + "wasm-encoder 0.227.1", ] [[package]] name = "wat" -version = "1.227.0" +version = "1.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc4d0762d835bf25be727f4320a8b08ce4451fb1e5868940ad7708503c6a6c9" +checksum = "b3d394d5bef7006ff63338d481ca10f1af76601e65ebdf5ed33d29302994e9cc" dependencies = [ - "wast 227.0.0", + "wast 227.0.1", ] [[package]] @@ -2532,9 +2685,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] @@ -2564,12 +2717,10 @@ dependencies = [ "anyhow", "clap", "heck 0.5.0", - "test-helpers", - "wasm-encoder 0.227.0", + "wasm-encoder 0.227.1", "wasm-metadata", "wit-bindgen-core", "wit-component", - "wit-parser 0.227.0", ] [[package]] @@ -2580,8 +2731,8 @@ dependencies = [ "clap", "heck 0.5.0", "test-artifacts", - "wasm-encoder 0.227.0", - "wasmparser 0.227.0", + "wasm-encoder 0.227.1", + "wasmparser 0.227.1", "wasmtime", "wasmtime-wasi", "wit-bindgen-c", @@ -2590,8 +2741,9 @@ dependencies = [ "wit-bindgen-markdown", "wit-bindgen-moonbit", "wit-bindgen-rust", + "wit-bindgen-test", "wit-component", - "wit-parser 0.227.0", + "wit-parser 0.227.1", ] [[package]] @@ -2600,7 +2752,7 @@ version = "0.40.0" dependencies = [ "anyhow", "heck 0.5.0", - "wit-parser 0.227.0", + "wit-parser 0.227.1", ] [[package]] @@ -2612,12 +2764,12 @@ dependencies = [ "heck 0.5.0", "indexmap", "test-helpers", - "wasm-encoder 0.227.0", + "wasm-encoder 0.227.1", "wasm-metadata", - "wasmparser 0.227.0", + "wasmparser 0.227.1", "wit-bindgen-core", "wit-component", - "wit-parser 0.227.0", + "wit-parser 0.227.1", ] [[package]] @@ -2685,11 +2837,32 @@ dependencies = [ "wit-bindgen-rust", ] +[[package]] +name = "wit-bindgen-test" +version = "0.40.0" +dependencies = [ + "anyhow", + "clap", + "heck 0.5.0", + "log", + "rayon", + "regex", + "serde", + "toml", + "wasi-preview1-component-adapter-provider", + "wasm-compose", + "wasm-encoder 0.227.1", + "wasmparser 0.227.1", + "wat", + "wit-component", + "wit-parser 0.227.1", +] + [[package]] name = "wit-component" -version = "0.227.0" +version = "0.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b77d5d7ce899af259d77309a5c9d54fc450c43d7014d08e0eccaf742fd582c1" +checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" dependencies = [ "anyhow", "bitflags", @@ -2698,11 +2871,11 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.227.0", + "wasm-encoder 0.227.1", "wasm-metadata", - "wasmparser 0.227.0", + "wasmparser 0.227.1", "wat", - "wit-parser 0.227.0", + "wit-parser 0.227.1", ] [[package]] @@ -2725,9 +2898,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.227.0" +version = "0.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd02ebcfdbbe83a4fc20991c31e8408a1dbb895194c81191e431f7bd0639545" +checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11" dependencies = [ "anyhow", "id-arena", @@ -2738,7 +2911,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.227.0", + "wasmparser 0.227.1", ] [[package]] @@ -2812,18 +2985,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", @@ -2855,27 +3028,27 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.14+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 6764cc4a1..ed2b220ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,13 @@ prettyplease = "0.2.20" syn = { version = "2.0.89", features = ["printing"] } futures = "0.3.31" +wat = "1.227.0" wasmparser = "0.227.0" wasm-encoder = "0.227.0" wasm-metadata = "0.227.0" wit-parser = "0.227.0" wit-component = "0.227.0" +wasm-compose = "0.227.0" wit-bindgen-core = { path = 'crates/core', version = '0.40.0' } wit-bindgen-c = { path = 'crates/c', version = '0.40.0' } @@ -46,19 +48,21 @@ wit-bindgen-csharp = { path = 'crates/csharp', version = '0.40.0' } wit-bindgen-markdown = { path = 'crates/markdown', version = '0.40.0' } wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.40.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.40.0', default-features = false } +wit-bindgen-test = { path = 'crates/test', version = '0.40.0' } [[bin]] name = "wit-bindgen" [dependencies] anyhow = { workspace = true } -clap = { workspace = true } +clap = { workspace = true, features = ['wrap_help'] } wit-bindgen-core = { workspace = true } wit-bindgen-rust = { workspace = true, features = ['clap'], optional = true } wit-bindgen-c = { workspace = true, features = ['clap'], optional = true } wit-bindgen-markdown = { workspace = true, features = ['clap'], optional = true } wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true } wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-test = { workspace = true } wit-component = { workspace = true } wasm-encoder = { workspace = true } diff --git a/ci/publish.rs b/ci/publish.rs index 1b8566256..84a6ebce4 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -26,6 +26,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wit-bindgen-rust-macro", "wit-bindgen-rt", "wit-bindgen", + "wit-bindgen-test", "wit-bindgen-cli", ]; diff --git a/crates/c/Cargo.toml b/crates/c/Cargo.toml index cbf7cdc98..ae698fca9 100644 --- a/crates/c/Cargo.toml +++ b/crates/c/Cargo.toml @@ -23,7 +23,3 @@ wasm-metadata = { workspace = true } anyhow = { workspace = true } heck = { workspace = true } clap = { workspace = true, optional = true } - -[dev-dependencies] -test-helpers = { path = '../test-helpers' } -wit-parser = { workspace = true } diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 6d1ae0671..57b57fe7a 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -63,7 +63,7 @@ impl std::fmt::Display for Enabled { } #[derive(Default, Debug, Clone)] -#[cfg_attr(feature = "clap", derive(clap::Args))] +#[cfg_attr(feature = "clap", derive(clap::Parser))] pub struct Opts { /// Skip emitting component allocation helper functions #[cfg_attr(feature = "clap", arg(long))] diff --git a/crates/c/tests/codegen.rs b/crates/c/tests/codegen.rs deleted file mode 100644 index 359217cb1..000000000 --- a/crates/c/tests/codegen.rs +++ /dev/null @@ -1,156 +0,0 @@ -use anyhow::Result; -use heck::*; -use std::env; -use std::path::{Path, PathBuf}; -use std::process::Command; -use wit_parser::{Resolve, UnresolvedPackageGroup}; - -macro_rules! codegen_test { - // TODO: implement support for stream, future, and error-context, and then - // remove these lines: - (streams $name:tt $test:tt) => {}; - (futures $name:tt $test:tt) => {}; - (resources_with_streams $name:tt $test:tt) => {}; - (resources_with_futures $name:tt $test:tt) => {}; - (error_context $name:tt $test:tt) => {}; - - ($id:ident $name:tt $test:tt) => { - #[test] - fn $id() { - test_helpers::run_world_codegen_test( - "guest-c", - $test.as_ref(), - |resolve, world, files| { - wit_bindgen_c::Opts::default() - .build() - .generate(resolve, world, files) - .unwrap() - }, - verify, - ); - test_helpers::run_world_codegen_test( - "guest-c-no-sig-flattening", - $test.as_ref(), - |resolve, world, files| { - let mut opts = wit_bindgen_c::Opts::default(); - opts.no_sig_flattening = true; - opts.build().generate(resolve, world, files).unwrap() - }, - verify, - ); - test_helpers::run_world_codegen_test( - "guest-c-autodrop-borrows", - $test.as_ref(), - |resolve, world, files| { - let mut opts = wit_bindgen_c::Opts::default(); - opts.autodrop_borrows = wit_bindgen_c::Enabled::Yes; - opts.build().generate(resolve, world, files).unwrap() - }, - verify, - ); - } - }; -} - -test_helpers::codegen_tests!(); - -fn verify(dir: &Path, name: &str) { - let name = name.to_snake_case(); - let sdk_path = PathBuf::from( - env::var_os("WASI_SDK_PATH").expect("environment variable WASI_SDK_PATH should be set"), - ); - let sysroot = sdk_path.join("share/wasi-sysroot"); - let c_src = dir.join(format!("{name}.c")); - - let shared_args = vec![ - "--sysroot", - sysroot.to_str().unwrap(), - "-I", - dir.to_str().unwrap(), - "-Wall", - "-Wextra", - "-Wc++-compat", - "-Werror", - "-Wno-unused-parameter", - "-c", - "-o", - ]; - - let mut cmd = Command::new(sdk_path.join("bin/clang")); - cmd.args(&shared_args); - cmd.arg(dir.join("obj.o")); - cmd.arg(&c_src); - test_helpers::run_command(&mut cmd); - - let cpp_src = c_src.with_extension("cpp"); - std::fs::write(&cpp_src, format!("#include \"{name}.h\"\n")).unwrap(); - let mut cmd = Command::new(sdk_path.join("bin/clang++")); - cmd.args(&shared_args); - cmd.arg(dir.join("obj.o")); - cmd.arg(&cpp_src); - test_helpers::run_command(&mut cmd); -} - -#[test] -fn rename_option() -> Result<()> { - let dir = test_helpers::test_directory("codegen", "guest-c", "rename-option"); - - let mut opts = wit_bindgen_c::Opts::default(); - opts.rename.push(("a".to_string(), "rename1".to_string())); - opts.rename - .push(("foo:bar/b".to_string(), "rename2".to_string())); - opts.rename.push(("c".to_string(), "rename3".to_string())); - - let mut resolve = Resolve::default(); - let pkg = resolve.push_group(UnresolvedPackageGroup::parse( - "input.wit", - r#" - package foo:bar; - - interface b { - f: func(); - } - - world rename-option { - import a: interface { - f: func(); - } - import b; - - export run: func(); - - export c: interface { - f: func(); - } - export b; - } - "#, - )?)?; - let world = resolve.select_world(pkg, None)?; - let mut files = Default::default(); - opts.build().generate(&resolve, world, &mut files)?; - for (file, contents) in files.iter() { - let dst = dir.join(file); - std::fs::create_dir_all(dst.parent().unwrap()).unwrap(); - std::fs::write(&dst, contents).unwrap(); - } - - std::fs::write( - dir.join("rename_option.c"), - r#" -#include "rename_option.h" - -void rename_option_run(void) { - rename1_f(); - rename2_f(); -} - -void rename3_f() {} - -void exports_rename2_f() {} - "#, - )?; - - verify(&dir, "rename-option"); - Ok(()) -} diff --git a/crates/guest-rust/rt/build.rs b/crates/guest-rust/rt/build.rs index b0d5d1831..4bfbed9e0 100644 --- a/crates/guest-rust/rt/build.rs +++ b/crates/guest-rust/rt/build.rs @@ -26,6 +26,6 @@ fn main() { std::fs::copy(&src, &dst).unwrap(); - println!("cargo:rustc-link-lib={dst_name}"); + println!("cargo:rustc-link-lib=static={dst_name}"); println!("cargo:rustc-link-search=native={}", out_dir.display()); } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index d180e66c9..1089ddb0b 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -302,7 +302,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.r#gen.return_pointer_area_align = self.r#gen.return_pointer_area_align.max(align); uwriteln!( self.src, - "let ptr{tmp} = _RET_AREA.0.as_mut_ptr().cast::();" + "let ptr{tmp} = (&raw mut _RET_AREA.0).cast::();" ); } format!("ptr{}", tmp) diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 58e47dba9..7024679ee 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -477,7 +477,7 @@ macro_rules! {macro_name} {{ let module = format!( "\ {docs} - #[allow(dead_code, unused_imports, clippy::all)] + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] pub mod {snake} {{ {used_static} {module} diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index cfaebda24..2e3bcbdff 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -180,7 +180,7 @@ fn parse_async(s: &str) -> Result { } #[derive(Default, Debug, Clone)] -#[cfg_attr(feature = "clap", derive(clap::Args))] +#[cfg_attr(feature = "clap", derive(clap::Parser))] pub struct Opts { /// Whether or not a formatter is executed to format generated code. #[cfg_attr(feature = "clap", arg(long))] @@ -245,7 +245,7 @@ pub struct Opts { /// specified multiple times to add multiple attributes. /// /// These derive attributes will be added to any generated structs or enums - #[cfg_attr(feature = "clap", arg(long = "additional_derive_attribute", short = 'd', default_values_t = Vec::::new()))] + #[cfg_attr(feature = "clap", arg(long, short = 'd'))] pub additional_derive_attributes: Vec, /// Variants and records to ignore when applying additional derive attributes. @@ -254,7 +254,7 @@ pub struct Opts { /// This feature allows some variants and records to use types for which adding traits will cause /// compilation to fail, such as serde::Deserialize on wasi:io/streams. /// - #[cfg_attr(feature = "clap", arg(long = "additional_derive_ignore", default_values_t = Vec::::new()))] + #[cfg_attr(feature = "clap", arg(long))] pub additional_derive_ignore: Vec, /// Remapping of wit interface and type names to Rust module names and types. diff --git a/crates/rust/tests/codegen.rs b/crates/rust/tests/codegen.rs index 46b2379df..58e84cd6e 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -1,646 +1,6 @@ #![allow(unused_macros)] #![allow(dead_code, unused_variables)] -mod codegen_tests { - macro_rules! codegen_test { - (wasi_cli $name:tt $test:tt) => {}; - (wasi_http $name:tt $test:tt) => {}; - ($id:ident $name:tt $test:tt) => { - mod $id { - wit_bindgen::generate!({ - path: $test, - stubs, - generate_all - }); - - // This empty module named 'core' is here to catch module path - // conflicts with 'core' modules used in code generated by the - // wit_bindgen::generate macro. - // Ref: https://github.com/bytecodealliance/wit-bindgen/pull/568 - mod core {} - - #[test] - fn works() {} - - mod borrowed { - wit_bindgen::generate!({ - path: $test, - ownership: Borrowing { - duplicate_if_necessary: false - }, - stubs, - export_prefix: "[borrowed]", - generate_all - }); - - #[test] - fn works() {} - } - - mod duplicate { - wit_bindgen::generate!({ - path: $test, - ownership: Borrowing { - duplicate_if_necessary: true - }, - stubs, - export_prefix: "[duplicate]", - generate_all - }); - - #[test] - fn works() {} - } - - mod async_ { - wit_bindgen::generate!({ - path: $test, - stubs, - export_prefix: "[async-prefix]", - generate_all, - async: true, - }); - - #[test] - fn works() {} - } - } - - }; - } - test_helpers::codegen_tests!(); -} - -mod strings { - wit_bindgen::generate!({ - inline: " - package my:strings; - - world not-used-name { - import cat: interface { - foo: func(x: string); - bar: func() -> string; - } - } - ", - }); - - #[allow(dead_code)] - fn test() { - // Test the argument is `&str`. - cat::foo("hello"); - - // Test the return type is `String`. - let _t: String = cat::bar(); - } -} - -/// Like `strings` but with a type alias. -mod aliased_strings { - wit_bindgen::generate!({ - inline: " - package my:strings; - - world not-used-name { - import cat: interface { - type my-string = string; - foo: func(x: my-string); - bar: func() -> my-string; - } - } - ", - }); - - #[allow(dead_code)] - fn test() { - // Test the argument is `&str`. - cat::foo("hello"); - - // Test the return type is `String`. - let _t: String = cat::bar(); - } -} - -/// Like `aliased_string` but with lists instead of strings. -mod aliased_lists { - wit_bindgen::generate!({ - inline: " - package my:strings; - - world not-used-name { - import cat: interface { - type my-list = list; - foo: func(x: my-list); - bar: func() -> my-list; - } - } - ", - }); - - #[allow(dead_code)] - fn test() { - // Test the argument is `&[u8]`. - cat::foo(b"hello"); - - // Test the return type is `Vec`. - let _t: Vec = cat::bar(); - } -} - -mod run_ctors_once_workaround { - wit_bindgen::generate!({ - inline: " - package my:strings; - - world not-used-name { - export apply-the-workaround: func(); - } - ", - disable_run_ctors_once_workaround: true, - stubs, - }); -} - -/// Like `strings` but with `raw_strings`. -mod raw_strings { - wit_bindgen::generate!({ - inline: " - package my:raw-strings; - - world not-used-name { - import cat: interface { - foo: func(x: string); - bar: func() -> string; - } - } - ", - raw_strings, - }); - - #[allow(dead_code)] - fn test() { - // Test the argument is `&[u8]`. - cat::foo(b"hello"); - - // Test the return type is `Vec`. - let _t: Vec = cat::bar(); - } -} - -mod skip { - wit_bindgen::generate!({ - inline: " - package my:inline; - - world baz { - export exports: interface { - foo: func(); - bar: func(); - } - } - ", - skip: ["foo"], - }); - - struct Component; - - impl exports::exports::Guest for Component { - fn bar() {} - } - - export!(Component); -} - -mod symbol_does_not_conflict { - wit_bindgen::generate!({ - inline: " - package my:inline; - - interface foo1 { - foo: func(); - } - - interface foo2 { - foo: func(); - } - - interface bar1 { - bar: func() -> string; - } - - interface bar2 { - bar: func() -> string; - } - - world foo { - export foo1; - export foo2; - export bar1; - export bar2; - } - ", - }); - - struct Component; - - impl exports::my::inline::foo1::Guest for Component { - fn foo() {} - } - - impl exports::my::inline::foo2::Guest for Component { - fn foo() {} - } - - impl exports::my::inline::bar1::Guest for Component { - fn bar() -> String { - String::new() - } - } - - impl exports::my::inline::bar2::Guest for Component { - fn bar() -> String { - String::new() - } - } - - export!(Component); -} - -mod alternative_bitflags_path { - wit_bindgen::generate!({ - inline: " - package my:inline; - world foo { - flags bar { - foo, - bar, - baz - } - export get-flag: func() -> bar; - } - ", - bitflags_path: "my_bitflags", - }); - - pub(crate) use wit_bindgen::bitflags as my_bitflags; - - struct Component; - - export!(Component); - - impl Guest for Component { - fn get_flag() -> Bar { - Bar::BAZ - } - } -} - -mod owned_resource_deref_mut { - wit_bindgen::generate!({ - inline: " - package my:inline; - - interface foo { - resource bar { - constructor(data: u32); - get-data: func() -> u32; - consume: static func(%self: bar) -> u32; - } - } - - world baz { - export foo; - } - ", - }); - - pub struct MyResource { - data: u32, - } - - impl exports::my::inline::foo::GuestBar for MyResource { - fn new(data: u32) -> Self { - Self { data } - } - - fn get_data(&self) -> u32 { - self.data - } - - fn consume(mut this: exports::my::inline::foo::Bar) -> u32 { - let me: &MyResource = this.get(); - let prior_data: &u32 = &me.data; - let new_data = prior_data + 1; - let me: &mut MyResource = this.get_mut(); - let mutable_data: &mut u32 = &mut me.data; - *mutable_data = new_data; - me.data - } - } - - struct Component; - - impl exports::my::inline::foo::Guest for Component { - type Bar = MyResource; - } - - export!(Component); -} - -mod package_with_versions { - wit_bindgen::generate!({ - inline: " - package my:inline@0.0.0; - - interface foo { - resource bar { - constructor(); - } - } - - world baz { - export foo; - } - ", - }); - - pub struct MyResource; - - impl exports::my::inline::foo::GuestBar for MyResource { - fn new() -> Self { - loop {} - } - } - - struct Component; - - impl exports::my::inline::foo::Guest for Component { - type Bar = MyResource; - } - - export!(Component); -} - -mod custom_derives { - use std::collections::{hash_map::RandomState, HashSet}; - - wit_bindgen::generate!({ - inline: " - package my:inline; - - interface blag { - resource input-stream { - read: func(len: u64) -> list; - } - } - - interface blah { - use blag.{input-stream}; - record foo { - field1: string, - field2: list - } - - bar: func(cool: foo); - - variant ignoreme { - stream-type(input-stream), - } - - barry: func(warm: ignoreme); - } - - world baz { - export blah; - } - ", - - // Clone is included by default almost everywhere, so include it here to make sure it - // doesn't conflict - additional_derives: [serde::Serialize, serde::Deserialize, Hash, Clone, PartialEq, Eq], - additional_derives_ignore: ["ignoreme"], - }); - - use exports::my::inline::blah::{Foo, Ignoreme}; - - struct Component; - - impl exports::my::inline::blah::Guest for Component { - fn bar(cool: Foo) { - // Check that built in derives that I've added actually work by seeing that this hashes - let _blah: HashSet = HashSet::from_iter([Foo { - field1: "hello".to_string(), - field2: vec![1, 2, 3], - }]); - - // Check that the attributes from an external crate actually work. If they don't work, - // compilation will fail here - let _ = serde_json::to_string(&cool); - } - - fn barry(warm: Ignoreme) { - // Compilation would fail if serde::Deserialize was applied to Ignoreme - let _ = warm; - } - } - - export!(Component); -} - -mod with { - wit_bindgen::generate!({ - inline: " - package my:inline; - - interface foo { - record msg { - field: string, - } - } - - interface bar { - use foo.{msg}; - - bar: func(m: msg); - } - - world baz { - import bar; - } - ", - with: { - "my:inline/foo": other::my::inline::foo, - }, - }); - - pub mod other { - wit_bindgen::generate!({ - inline: " - package my:inline; - - interface foo { - record msg { - field: string, - } - } - - world dummy { - use foo.{msg}; - import bar: func(m: msg); - } - ", - }); - } - - #[allow(dead_code)] - fn test() { - let msg = other::my::inline::foo::Msg { - field: "hello".to_string(), - }; - my::inline::bar::bar(&msg); - } -} - -mod with_and_resources { - wit_bindgen::generate!({ - inline: " - package my:inline; - - interface foo { - resource a; - } - - interface bar { - use foo.{a}; - - bar: func(m: a) -> list; - } - - world baz { - import bar; - } - ", - with: { - "my:inline/foo": other::my::inline::foo, - }, - }); - - pub mod other { - wit_bindgen::generate!({ - inline: " - package my:inline; - - interface foo { - resource a; - } - - world dummy { - use foo.{a}; - import bar: func(m: a); - } - ", - }); - } -} - -#[allow(unused)] -mod generate_unused_types { - use exports::foo::bar::component::UnusedEnum; - use exports::foo::bar::component::UnusedRecord; - use exports::foo::bar::component::UnusedVariant; - - wit_bindgen::generate!({ - inline: " - package foo:bar; - - world bindings { - export component; - } - - interface component { - variant unused-variant { - %enum(unused-enum), - %record(unused-record) - } - enum unused-enum { - unused - } - record unused-record { - x: u32 - } - } - ", - generate_unused_types: true, - }); -} - -#[allow(unused)] -mod gated_features { - wit_bindgen::generate!({ - inline: r#" - package foo:bar@1.2.3; - - world bindings { - @unstable(feature = x) - import x: func(); - @unstable(feature = y) - import y: func(); - @since(version = 1.2.3) - import z: func(); - } - "#, - features: ["y"], - }); - - fn _foo() { - y(); - z(); - } -} - -#[allow(unused)] -mod simple_with_option { - mod a { - wit_bindgen::generate!({ - inline: r#" - package foo:bar; - - interface a { - x: func(); - } - - package foo:baz { - world w { - import foo:bar/a; - } - } - "#, - world: "foo:baz/w", - generate_all, - }); - } - - mod b { - wit_bindgen::generate!({ - inline: r#" - package foo:bar; - - interface a { - x: func(); - } - - package foo:baz { - world w { - import foo:bar/a; - } - } - "#, - world: "foo:baz/w", - with: { "foo:bar/a": generate }, - }); - } -} - #[allow(unused)] mod multiple_paths { wit_bindgen::generate!({ @@ -656,153 +16,3 @@ mod multiple_paths { generate_all, }); } - -#[allow(unused)] -mod generate_custom_section_link_helpers { - wit_bindgen::generate!({ - inline: r#" - package a:b; - - world test { - import a: interface { - x: func(); - } - } - "#, - disable_custom_section_link_helpers: true, - }); -} - -mod with_type { - - mod my_types { - #[derive(Debug, Clone, Copy)] - pub struct MyA { - pub inner: f64, - } - - #[derive(Debug, Clone, Copy)] - pub struct MyB; - - impl MyB { - pub fn take_handle(&self) -> u32 { - 0 - } - - pub fn from_handle(handle: u32) -> Self { - Self - } - } - - pub enum MyC { - A(MyA), - B(MyB), - } - - pub struct MyD { - pub inner: u32, - } - - pub struct MyE { - pub inner: u32, - } - } - - wit_bindgen::generate!({ - inline: " - package my:inline; - - interface foo { - - record a { - inner: f64, - } - - resource b; - - variant c { - a(a), - b(b), - } - - // test type definition generation with `generate` option - record f { - inner: u32, - } - - // test type definition generation without `generate` option - record g { - inner: u32, - } - - func1: func(v: a) -> a; - func2: func(v: b) -> b; - func3: func(v: list) -> list; - func4: func(v: option) -> option; - func5: func() -> result; - func6: func() -> result; - func7: func() -> result; - } - - interface bar { - record e { - inner: u32, - } - - func6: func(v: e) -> e; - } - - world dummy-type-remap { - // test remapping with importing type directly - use foo.{c}; - import func7: func(v: c) -> c; - - // test remapping the type defined in world - record d { - inner: u32, - } - - import func8: func(v: d) -> d; - - export bar; - } - ", - with: { - "my:inline/foo/a": crate::with_type::my_types::MyA, - "my:inline/foo/b": crate::with_type::my_types::MyB, - "my:inline/foo/c": crate::with_type::my_types::MyC, - "dummy-type-remap/d": crate::with_type::my_types::MyD, - "my:inline/bar/e": crate::with_type::my_types::MyE, - "my:inline/foo/f": generate, - }, - }); - - pub struct Guest; - - impl exports::my::inline::bar::Guest for Guest { - fn func6(v: my_types::MyE) -> my_types::MyE { - v - } - } - - fn test() { - let a = my_types::MyA { inner: 0.0 }; - let _ = my::inline::foo::func1(a); - - let b = my_types::MyB; - let _ = my::inline::foo::func2(b); - - let c = my_types::MyC::A(a); - let _ = func7(c); - - let a_list = vec![a, a]; - let _ = my::inline::foo::func3(&a_list); - - let _ = my::inline::foo::func4(Some(a)); - - let _ = my::inline::foo::func5(); - - let d = my_types::MyD { inner: 0 }; - let _ = func8(d); - } -} diff --git a/crates/rust/tests/codegen_no_std.rs b/crates/rust/tests/codegen_no_std.rs deleted file mode 100644 index 362931c92..000000000 --- a/crates/rust/tests/codegen_no_std.rs +++ /dev/null @@ -1,119 +0,0 @@ -//! Like `codegen_tests` in codegen.rs, but with no_std. -//! -//! We use `std_feature` and don't enable the "std" feature. - -#![no_std] -#![allow(unused_macros)] -#![allow(dead_code, unused_variables)] - -extern crate alloc; - -mod codegen_tests { - macro_rules! codegen_test { - (wasi_cli $name:tt $test:tt) => {}; - (wasi_http $name:tt $test:tt) => {}; - - // TODO: We should be able to support streams, futures, and - // error-contexts in no_std mode if desired, but haven't done the work - // yet. - (streams $name:tt $test:tt) => {}; - (futures $name:tt $test:tt) => {}; - (resources_with_streams $name:tt $test:tt) => {}; - (resources_with_futures $name:tt $test:tt) => {}; - (error_context $name:tt $test:tt) => {}; - - ($id:ident $name:tt $test:tt) => { - mod $id { - wit_bindgen::generate!({ - path: $test, - std_feature, - stubs, - generate_all - }); - - #[test] - fn works() {} - } - - }; - } - test_helpers::codegen_tests!(); -} - -mod strings { - use alloc::string::String; - - wit_bindgen::generate!({ - inline: " - package my:strings; - world not-used-name { - import cat: interface { - foo: func(x: string); - bar: func() -> string; - } - } - ", - std_feature, - }); - - #[allow(dead_code)] - fn test() { - // Test the argument is `&str`. - cat::foo("hello"); - - // Test the return type is `String`. - let _t: String = cat::bar(); - } -} - -/// Like `strings` but with raw_strings`. -mod raw_strings { - use alloc::vec::Vec; - - wit_bindgen::generate!({ - inline: " - package raw:strings; - world not-used-name { - import cat: interface { - foo: func(x: string); - bar: func() -> string; - } - } - ", - raw_strings, - std_feature, - }); - - #[allow(dead_code)] - fn test() { - // Test the argument is `&[u8]`. - cat::foo(b"hello"); - - // Test the return type is `Vec`. - let _t: Vec = cat::bar(); - } -} - -mod skip { - wit_bindgen::generate!({ - inline: " - package foo:foo; - world baz { - export exports: interface { - foo: func(); - bar: func(); - } - } - ", - skip: ["foo"], - std_feature, - }); - - struct Component; - - impl exports::exports::Guest for Component { - fn bar() {} - } - - export!(Component); -} diff --git a/crates/test-rust-wasm/Cargo.toml b/crates/test-rust-wasm/Cargo.toml index 63db4d1cb..015e510eb 100644 --- a/crates/test-rust-wasm/Cargo.toml +++ b/crates/test-rust-wasm/Cargo.toml @@ -16,10 +16,6 @@ doctest = false name = "smoke" test = false -[[bin]] -name = "numbers" -test = false - [[bin]] name = "records" test = false @@ -28,10 +24,6 @@ test = false name = "variants" test = false -[[bin]] -name = "lists" -test = false - [[bin]] name = "flavorful" test = false diff --git a/crates/test-rust-wasm/src/bin/lists.rs b/crates/test-rust-wasm/src/bin/lists.rs deleted file mode 100644 index 2f58681d3..000000000 --- a/crates/test-rust-wasm/src/bin/lists.rs +++ /dev/null @@ -1,3 +0,0 @@ -include!("../../../../tests/runtime/lists/wasm.rs"); - -fn main() {} diff --git a/crates/test-rust-wasm/src/bin/numbers.rs b/crates/test-rust-wasm/src/bin/numbers.rs deleted file mode 100644 index 3589a5604..000000000 --- a/crates/test-rust-wasm/src/bin/numbers.rs +++ /dev/null @@ -1,3 +0,0 @@ -include!("../../../../tests/runtime/numbers/wasm.rs"); - -fn main() {} diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml new file mode 100644 index 000000000..398a39d81 --- /dev/null +++ b/crates/test/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "wit-bindgen-test" +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +homepage = 'https://github.com/bytecodealliance/wit-bindgen' +description = """ +Backend of the `wit-bindgne test` subcommand +""" +readme = "README.md" + +[lib] +test = false +doctest = false + +[dependencies] +anyhow = { workspace = true } +clap = { workspace = true, features = ['env'] } +heck = { workspace = true } +log = "0.4.26" +rayon = "1.10.0" +regex = "1.11.1" +serde = { version = "1.0.218", features = ["derive"] } +toml = "0.8.20" +wasi-preview1-component-adapter-provider = "30.0.2" +wasm-compose = { workspace = true } +wasm-encoder = { workspace = true } +wasmparser = { workspace = true, features = ["features"] } +wat = { workspace = true } +wit-component = { workspace = true } +wit-parser = { workspace = true } diff --git a/crates/test/README.md b/crates/test/README.md new file mode 100644 index 000000000..0e0348f32 --- /dev/null +++ b/crates/test/README.md @@ -0,0 +1,238 @@ +# `wit-bindgen-test` + +This folder contains the `wit-bindgen-test` crate which is used to power the +`wit-bindgen test` subcommand of the `wit-bindgen` CLI. The purpose of this +document is to document what this subcommand does and how it enables testing. + +## Testing `wit-bindgen` + +The goal of the `test` subcommand is to make it as easy as possible to test +bindings generators and their functionality. It's also intended to enable +testing various kinds of functionality of a bindings generator in terms of +code generator flags, language flags, etc. This is a pretty generic problem +though since this isn't just one bindings generator but instead a bindings +generator for many languages. There's additional overlap where component runtime +hosts may want similar tests as everything, if you squint hard enough, looks +like it's all testing in a similar fashion. + +To help scope this problem down, the goals of this tool are: + +* Enable easily testing **guest** bindings generators, aka those that compile + code to WebAssembly and produce a WebAssembly component. +* One category of tests are **codegen tests**. Codegen tests take a `*.wit` file + as input and assert that both bindings can be generated and the generated + bindings are valid. Validity of the generated bindings is determined by the + guest language itself, and this step typically doesn't involve creating a + WebAssembly component binary. +* The second category of tests are **runtime tests**. Runtime tests are + goverened by a `test.wit` which contains a `runner` world and a `test` world. + There are then "runner" components defined with the file prefix `runner`, for + example `runner.rs`. To complement these there are "test" components, such as + `test.rs`. The runner is composed with the test to create a single component + binary which looks like a WASI CLI program. This program is then run to + completion. + +Built on these goals the `wit-bindgen test` subcommand has a number of sub-goals +such as parallelizing tests, making iteration fast, good error messages, etc. +This is all feeding towards the highest-level goal of making it easy to write +tests in any language that has a bindings generator and can compile to +components. + +## CLI Interface + +The `wit-bindgen test` subcommand can be explored with: + +``` +$ wit-bindgen test -h +``` + +The main arguments to the subcommand are directories that contain tests and a +`--artifacts` path which contains where to store temporary build artifacts, such +as compiled component binaries. For example: + +``` +$ wit-bindgen test ./tests --artifacts ./target/artifacts +``` + +This will look recursively in the `./tests` directory for tests. Test discovery +is detailed below in runtime and codegen tests. During test execution any +intermediate artifacts are present in `./target/artifacts` at a per-test stable +location to assist with debugging. For example if invalid code is generated it +can inspected within `./target/artifacts`. + +Some other basic flags are: + +* `-f` or `--filter` - a regex-based filter on which tests to run, used to run + only a single test if desired. Note that running a single test can also be + done by passing a narrower `./tests` directory, such as + `./tests/codegen/my-test.wit`. + +* `-i` or `--inherit-stderr` - this is used to have subprocesses inherit stderr + from the calling process which can be useful when guest language compilers + produce colored error messages for example as otherwise stderr is captured + from subcommands by default meaning that colors won't show up. + +* `-l` or `--languages` - By default all languages that `wit-bindgen test` + supports are enabled. This means that if you don't have development toolchains + installed locally tests may fail. This flag can be used to filter languages to + test (e.g. `--languages rust`) or to disable specific languages (e.g. + `--languages=-rust`). + +* `--runner` - Runtime tests are executed within a WebAssembly component runtime + and this is the path to a custom runtime to use. By default `wasmtime` is used + but any other runtime can be supplied. + +## Codegen Tests + +The first category of tests that `wit-bindgen test` supports are called "codegen +tests". These tests are a `*.wit` file which contains a single `world` within +it. These files are used as input to a language bindings generator and then the +output is compiled by the target language to ensure that valid bindings were +generated. + +These tests do not produce a complete component. Instead the validity of the +generated bindings are up to the target language. For example in Rust the +bindings are compiled with `--crate-type rlib`, in C the bindings are compiled +to an object file, and interpreted languages might run various lints for +example. + +Codegen tests are discovered inside of a directory called `codegen`. Internally +all `codegen/*.wit` files are then run as tests. By default all supported +languages of `wit-bindgen test` are run for each `codegen/*.wit` test file. + +#### Testing code generator options + +By default each language only tests the default settings of the bindings +generator. To have all tests also tested with more options you'll want to update +the `LanguageMethods::codegen_test_variants` method. If this is a non-empty +array then each entry will run each codegen test through those options as well, +effectively testing codegen tests in more than one configuration. + +#### Ignoring classes tests + +Ignoring classes of tests can be done in the CLI tool by updating a few +locations: + +* Update `WitConfig` to contain a field for this class of test + that needs to be ignored (if it's not already present). +* Tag tests as belonging to this class of tests by adding a comment at the top + such as `//@ async = true` which would indicate that this uses async features. +* Update `LanguageMethods::should_fail_verify` for your language to ignore this + class of tests by checking the `WitConfig` config option and returning + `true` for "should fail" + +This will still run the test but an error will be expected. If an error is +generated then the test will be considered to have passed. If the test instead +passes, however, then the test will be considered to have failed and the +`should_fail_verify` method will need to be updated. + +#### Ignoring a single test + +If a single test is problematic and doesn't fall into a "class" of tests like +above then the `LanguageMethods::should_fail_verify` method should be updated +and the `name` field should be consulted. This is the name of the test itself +and that can be used to expect failure in individual tests. + +## Runtime Tests + +The second class of tests supported by `wit-bindgen test` is what are called +"runtime tests". The goal of runtime tests is to not only test that generated +code is valid but it additionally produces a valid component that works at +runtime. These tests have the following structure: + +``` +my-test/ + test.wit # WIT `test` and `runner` worlds + runner.rs # Implementation of `runner` in Rust + runner.c # Implementation of `runner` in C + test.go # Implementation of `test` in Go + test2.go # Another implementation of `test` in Go +``` + +Each folder must contain a `test.wit` file. This WIT file must contain at least +two worlds: `runner` and `test`. The `runner` world imports functionality and +the `test` world exports functionality. + +Each `runner*` file is compiled, using the language-specific toolchain and +bindings, into a component. This is then additionally done for the `test*` +files. Bindings are automatically generated and provided to the compilation +phase and each language has its own conventions of how to assemble everything +into a component. + +Once components are produced the matrix of `runner x test` is produced to +compose together. Each runner and test are composed to produce a single +component which is a test case. For example the above example would have four +test components produced: + +* `runner.rs x test.go` +* `runner.rs x test2.go` +* `runner.c x test.go` +* `runner.c x test2.go` + +Each test component is then run with the `--runner` CLI option (or `wasmtime` by +default). + +The `runner` component is expected to export a `wasi:cli/run` interface +according to language specific conventions (e.g. it has `fn main() { .. }` for +Rust). Both the `runner` and `test` component can access other WASI APIs such as +printing to standard out/err for debugging. + +It's recommended to write both "runner" and "test" components in the language +that you want to test. The "runner" component exercises the ability to import +WIT interfaces and call them while the "test" component exercises the ability to +export interfaces and have them called. + +#### Test Configuration + +Each source language file can be annotated with arguments to pass to the +bindings generation phase. This is done by having a comment at the top of the +file such as: + +```rust +//@ args = '--custom --arguments' + +fn main() { + // ... +} +``` + +This `//@` prefix indicates that test configuration present. The test +configuration deserializes via TOML to `RuntimeTestConfig`. The field used here +is `args` which are the CLI arguments to pass to `wit-bindgen rust ...` in this +case. This can be used to have specific source files test various options of a +bindings generator. + +Note that multiple runners are supported, so for example in one test Rust might +have `runner-std.rs` and `runner-nostd.rs` to test with and without the +`--std-feature` flag to the Rust bindings generator. Note that regardless of +bindings generator flags it's expected that the original `runner` or `test` +worlds are still adhered to. + +## Language Support + +Currently the `wit-bindgen test` CLI comes with built-in language support for a +few languages. Note that this does not include built-in toolchain support. For +example `wit-bindgen test` will still need access to a Rust toolchain to compile +Rust source files. + +* Rust +* C +* C++ +* Go (via TinyGo) +* WebAssembly Text (`*.wat`) + +Tests written in these languages can use `wit-bindgen test` natively and don't +need to otherwise provide anything else. Custom language support is +additionally supported at this time via the `--custom` CLI flag to +`wit-bindgen test`. For example if the CLI didn't natively have support for Rust +it could be specified as: + +``` +$ wit-bindgen test ./tests --artifacts-dir ./artifacts \ + --custom rs=./wit-bindgen-rust-runner +``` + +This would recognize the `rs` file extension and use the +`./wit-bindgen-rust-runner` script or binary to execute tests. The exact +interface to the tests is documented as part of `wit-bindgen test --help` for +the `--custom` argument. diff --git a/crates/test/src/c.rs b/crates/test/src/c.rs new file mode 100644 index 000000000..e5e15243b --- /dev/null +++ b/crates/test/src/c.rs @@ -0,0 +1,187 @@ +use crate::{Compile, Kind, LanguageMethods, Runner, Verify}; +use anyhow::Result; +use clap::Parser; +use heck::ToSnakeCase; +use std::env; +use std::path::PathBuf; +use std::process::Command; + +#[derive(Default, Debug, Clone, Parser)] +pub struct COpts { + /// Path to the installation of wasi-sdk + #[clap(long, env = "WASI_SDK_PATH", value_name = "PATH")] + wasi_sdk_path: Option, +} + +pub struct C; + +pub struct Cpp; + +fn clang(runner: &Runner<'_>) -> PathBuf { + match &runner.opts.c.wasi_sdk_path { + Some(path) => path.join("bin/wasm32-wasip2-clang"), + None => "wasm32-wasip2-clang".into(), + } +} + +fn clangpp(runner: &Runner<'_>) -> PathBuf { + match &runner.opts.c.wasi_sdk_path { + Some(path) => path.join("bin/wasm32-wasip2-clang++"), + None => "wasm32-wasip2-clang++".into(), + } +} + +impl LanguageMethods for C { + fn display(&self) -> &str { + "c" + } + + fn comment_prefix_for_test_config(&self) -> Option<&str> { + Some("//@") + } + + fn should_fail_verify( + &self, + _name: &str, + config: &crate::config::WitConfig, + _args: &[String], + ) -> bool { + config.async_ + } + + fn codegen_test_variants(&self) -> &[(&str, &[&str])] { + &[ + ("no-sig-flattening", &["--no-sig-flattening"]), + ("autodrop", &["--autodrop-borrows=yes"]), + ] + } + + fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + prepare(runner, clang(runner)) + } + + fn compile(&self, runner: &Runner<'_>, c: &Compile<'_>) -> Result<()> { + compile(runner, c, clang(runner)) + } + + fn verify(&self, runner: &Runner<'_>, v: &Verify<'_>) -> Result<()> { + verify(runner, v, clang(runner)) + } +} + +impl LanguageMethods for Cpp { + fn display(&self) -> &str { + "cpp" + } + + fn bindgen_name(&self) -> Option<&str> { + Some("c") + } + + fn comment_prefix_for_test_config(&self) -> Option<&str> { + Some("//@") + } + + fn should_fail_verify( + &self, + name: &str, + config: &crate::config::WitConfig, + args: &[String], + ) -> bool { + C.should_fail_verify(name, config, args) + } + + fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + prepare(runner, clangpp(runner)) + } + + fn compile(&self, runner: &Runner<'_>, c: &Compile<'_>) -> Result<()> { + compile(runner, c, clangpp(runner)) + } + + fn verify(&self, runner: &Runner<'_>, v: &Verify<'_>) -> Result<()> { + verify(runner, v, clangpp(runner)) + } +} + +fn prepare(runner: &mut Runner<'_>, compiler: PathBuf) -> Result<()> { + let cwd = env::current_dir()?; + let dir = cwd.join(&runner.opts.artifacts).join("c"); + + super::write_if_different(&dir.join("test.c"), "int main() { return 0; }")?; + + println!("Testing if `{}` works...", compiler.display()); + runner.run_command(Command::new(compiler).current_dir(&dir).arg("test.c"))?; + + Ok(()) +} + +fn compile(runner: &Runner<'_>, compile: &Compile<'_>, compiler: PathBuf) -> Result<()> { + // Compile the C-based bindings to an object file. + let bindings_object = compile.output.with_extension("bindings.o"); + let mut cmd = Command::new(clang(runner)); + cmd.arg( + compile + .bindings_dir + .join(format!("{}.c", compile.component.kind)), + ) + .arg("-I") + .arg(&compile.bindings_dir) + .arg("-Wall") + .arg("-Wextra") + .arg("-Werror") + .arg("-Wno-unused-parameter") + .arg("-c") + .arg("-o") + .arg(&bindings_object); + runner.run_command(&mut cmd)?; + + // Now compile the runner's source code to with the above object and the + // component-type object into a final component. + let mut cmd = Command::new(compiler); + cmd.arg(&compile.component.path) + .arg(&bindings_object) + .arg( + compile + .bindings_dir + .join(format!("{}_component_type.o", compile.component.kind)), + ) + .arg("-I") + .arg(&compile.bindings_dir) + .arg("-Wall") + .arg("-Wextra") + .arg("-Werror") + .arg("-Wc++-compat") + .arg("-Wno-unused-parameter") + .arg("-g") + .arg("-o") + .arg(&compile.output); + match compile.component.kind { + Kind::Runner => {} + Kind::Test => { + cmd.arg("-mexec-model=reactor"); + } + } + runner.run_command(&mut cmd)?; + Ok(()) +} + +fn verify(runner: &Runner<'_>, verify: &Verify<'_>, compiler: PathBuf) -> Result<()> { + let mut cmd = Command::new(compiler); + cmd.arg( + verify + .bindings_dir + .join(format!("{}.c", verify.world.to_snake_case())), + ) + .arg("-I") + .arg(&verify.bindings_dir) + .arg("-Wall") + .arg("-Wextra") + .arg("-Werror") + .arg("-Wc++-compat") + .arg("-Wno-unused-parameter") + .arg("-c") + .arg("-o") + .arg(verify.artifacts_dir.join("tmp.o")); + runner.run_command(&mut cmd) +} diff --git a/crates/test/src/config.rs b/crates/test/src/config.rs new file mode 100644 index 000000000..61f958240 --- /dev/null +++ b/crates/test/src/config.rs @@ -0,0 +1,117 @@ +//! Configuration support for tests. +//! +//! This module contains the various structures and type definitions which are +//! used to configure both runtime tests and codegen tests. +//! +//! Test configuration happens by parsing TOML-in-comments at the start of +//! source files. Configuration is delimited by being at the top of a source +//! file and prefixed with a language's line-comment syntax followed by `@`. For +//! example in Rust that would look like: +//! +//! ```text +//! //@ some-key = 'some-value' +//! +//! include!(...); +//! +//! // ... rest of the test here +//! ``` +//! +//! Here `some-key = 'some-value'` is the TOML to parse into configuration. +//! There are two kinds of configuration here defined in this file: +//! +//! * `RuntimeTestConfig` - this is for runtime tests or `test.rs` and +//! `runner.rs` for example. This configures per-language and per-compilation +//! options. +//! +//! * `WitConfig` - this is per-`*.wit` file either as a codegen test or a +//! `test.wit` input for runtime tests. + +use anyhow::Context; +use anyhow::Result; +use serde::de::DeserializeOwned; +use serde::Deserialize; + +/// Configuration that can be placed at the top of runtime tests in source +/// language files. This is currently language-agnostic. +#[derive(Default, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub struct RuntimeTestConfig { + /// Extra command line arguments to pass to the language-specific bindings + /// generator. + /// + /// This is either a string which is whitespace delimited or it's an array + /// of strings. By default no extra arguments are passed. + #[serde(default)] + pub args: StringList, + // + // Maybe add something like this eventually if necessary? For example plumb + // arbitrary configuration from tests to the "compile" backend. This would + // then thread through as `Compile` and could be used to pass compiler flags + // for example. + // + // lang: HashMap, + + // ... + // + // or alternatively could also have something dedicated like: + // compile_flags: StringList, + // + // unclear! This should be expanded on over time as necessary. +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum StringList { + String(String), + List(Vec), +} + +impl From for Vec { + fn from(list: StringList) -> Vec { + match list { + StringList::String(s) => s.split_whitespace().map(|s| s.to_string()).collect(), + StringList::List(s) => s, + } + } +} + +impl Default for StringList { + fn default() -> StringList { + StringList::List(Vec::new()) + } +} + +/// Configuration found in `*.wit` file either in codegen tests or in `test.wit` +/// files for runtime tests. +#[derive(Clone, Default, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub struct WitConfig { + /// Indicates that this WIT test uses the component model async features + /// and/or proposal. + /// + /// This can be used to help expect failure in languages that do not yet + /// support this proposal. + #[serde(default, rename = "async")] + pub async_: bool, + + /// When set to `true` disables the passing of per-language default bindgen + /// arguments. For example with Rust it avoids passing `--generate-all` by + /// default to bindings generation. + pub default_bindgen_args: Option, +} + +/// Parses the configuration `T` from `contents` in comments at the start of the +/// file where comments are lines prefixed by `comment`. +pub fn parse_test_config(contents: &str, comment: &str) -> Result +where + T: DeserializeOwned, +{ + let config_lines: Vec<_> = contents + .lines() + .take_while(|l| l.starts_with(comment)) + .map(|l| &l[comment.len()..]) + .collect(); + let config_text = config_lines.join("\n"); + + toml::from_str(&config_text).context("failed to parse the test configuration") +} diff --git a/crates/test/src/custom.rs b/crates/test/src/custom.rs new file mode 100644 index 000000000..c260d8640 --- /dev/null +++ b/crates/test/src/custom.rs @@ -0,0 +1,156 @@ +use crate::{Bindgen, Compile, LanguageMethods, Runner, Verify}; +use anyhow::{bail, Context, Result}; +use clap::Parser; +use std::env; +use std::path::Path; +use std::process::Command; + +#[derive(Default, Debug, Clone, Parser)] +pub struct CustomOpts { + /// Specifies how to compile programs not natively known to this executable. + /// + /// For example `--custom foo=my-foo-script.sh` will register that files + /// with the extension `foo` (e.g. `test.foo`) will be compiled with + /// `my-foo-script.sh` by this program. + /// + /// The script specified will be invoked with its first argument as one of + /// three values: + /// + /// * `prepare` - this is used to perform any one-time setup for an entire + /// test run, such as downloading artifacts. This has the `PREP_DIR` + /// environment variable set. + /// + /// * `bindgen` - this is used to perform bindings generation for the + /// program at-hand. This has the `WIT`, and `BINDINGS_DIR` env + /// vars set. + /// + /// * `compile` - this is used to perform an actual compilation which + /// creates a component. This has the `SOURCE`, `KIND`, `PREP_DIR`, + /// `BINDINGS_DIR`, `ARTIFACTS_DIR`, and `OUTPUT` env vars set. + /// + /// * `verify` - this is used to verify that generated bindings are valid, + /// but does not create an actual component necessarily. This has the + /// `WIT`, `BINDINGS_DIR`, and `ARTIFACTS_DIR` env vars set. + /// + /// Environment variables are used to communicate various bits and pieces of + /// data to scripts. Environment variables used are: + /// + /// * `PREP_DIR` - the output of the `prepare` step and also available + /// during the `compile` step. Used for once-per-test-run storage. + /// + /// * `WIT` - path to a `*.wit` file during the `bindgen` step. + /// + /// * `BINDINGS_DIR` - the output directory of `bindgen` and also inputs + /// to `compile`. + /// + /// * `SOURCE` - the source file being compiled as part of `compile`. + /// + /// * `KIND` - either `runner` or `test` as part of the `compile` step. + /// + /// * `ARTIFACTS_DIR` - temporary directory which contains `BINDINGS_DIR` + /// where temporary artifacts can be stored. Part of the `compile` step. + /// + /// * `OUTPUT` - where to place the final output component. + #[arg(long , value_name = "EXT=PATH", value_parser = parse_custom)] + pub custom: Vec<(String, String)>, +} + +fn parse_custom(s: &str) -> Result<(String, String)> { + let mut parts = s.splitn(2, '='); + Ok(( + parts.next().unwrap().to_string(), + parts + .next() + .context("must be of the form `a=b`")? + .to_string(), + )) +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Language { + extension: String, + script: String, +} + +impl Language { + pub fn lookup(runner: &Runner<'_>, language: &str) -> Result { + for (ext, script) in runner.opts.custom.custom.iter() { + if ext == language { + return Ok(Language { + extension: ext.to_string(), + script: script.to_string(), + }); + } + } + + bail!( + "file extension `{language}` is unknown, but you can pass \ + a script with `--custom {language}=my-script.sh` to get it working" + ) + } +} + +impl LanguageMethods for Language { + fn display(&self) -> &str { + &self.extension + } + + fn comment_prefix_for_test_config(&self) -> Option<&str> { + None + } + + fn should_fail_verify( + &self, + _name: &str, + _config: &crate::config::WitConfig, + _args: &[String], + ) -> bool { + false + } + + fn generate_bindings(&self, runner: &Runner<'_>, bindgen: &Bindgen, dir: &Path) -> Result<()> { + runner.run_command( + Command::new(&self.script) + .arg("bindgen") + .env("WIT", &bindgen.wit_path) + .env("BINDINGS_DIR", dir), + ) + } + + fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + let dir = env::current_dir()? + .join(&runner.opts.artifacts) + .join(&self.extension); + runner.run_command( + Command::new(&self.script) + .arg("prepare") + .env("PREP_DIR", &dir), + ) + } + + fn compile(&self, runner: &Runner<'_>, compile: &Compile<'_>) -> Result<()> { + let dir = env::current_dir()? + .join(&runner.opts.artifacts) + .join(&self.extension); + runner.run_command( + Command::new(&self.script) + .arg("compile") + .env("SOURCE", &compile.component.path) + .env("KIND", compile.component.kind.to_string()) + .env("PREP_DIR", &dir) + .env("BINDINGS_DIR", &compile.bindings_dir) + .env("ARTIFACTS_DIR", &compile.artifacts_dir) + .env("OUTPUT", &compile.output), + ) + } + + fn verify(&self, runner: &Runner<'_>, verify: &Verify<'_>) -> Result<()> { + runner.run_command( + Command::new(&self.script) + .arg("verify") + .env("WIT", verify.wit_test) + .env("BINDINGS_DIR", &verify.bindings_dir) + .env("ARTIFACTS_DIR", &verify.artifacts_dir), + ) + } +} diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs new file mode 100644 index 000000000..df48119d6 --- /dev/null +++ b/crates/test/src/lib.rs @@ -0,0 +1,1052 @@ +use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; +use rayon::prelude::*; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::fmt; +use std::fs; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::sync::Arc; +use wasm_encoder::{Encode, Section}; +use wit_component::{ComponentEncoder, StringEncoding}; + +mod c; +mod config; +mod custom; +mod runner; +mod rust; +mod wat; + +/// Tool to run tests that exercise the `wit-bindgen` bindings generator. +/// +/// This tool is used to (a) generate bindings for a target language, (b) +/// compile the bindings and source code to a wasm component, (c) compose a +/// "runner" and a "test" component together, and (d) execute this component to +/// ensure that it passes. This process is guided by filesystem structure which +/// must adhere to some conventions. +/// +/// * Tests are located in any directory that contains a `test.wit` description +/// of the WIT being tested. The `` argument to this command is walked +/// recursively to find `test.wit` files. +/// +/// * The `test.wit` file must have a `runner` world and a `test` world. The +/// "runner" should import interfaces that are exported by "test". +/// +/// * Adjacent to `test.wit` should be a number of `runner*.*` files. There is +/// one runner per source language, for example `runner.rs` and `runner.c`. +/// These are source files for the `runner` world. Source files can start with +/// `//@ ...` comments to deserialize into `config::RuntimeTestConfig`, +/// currently that supports: +/// +/// ```text +/// //@ args = ['--arguments', 'to', '--the', 'bindings', '--generator'] +/// ``` +/// +/// or +/// +/// ```text +/// //@ args = '--arguments to --the bindings --generator' +/// ``` +/// +/// * Adjacent to `test.wit` should also be a number of `test*.*` files. Like +/// runners there is one per source language. Note that you can have multiple +/// implementations of tests in the same language too, for example +/// `test-foo.rs` and `test-bar.rs`. All tests must export the same `test` +/// world from `test.wit`, however. +/// +/// This tool will discover `test.wit` files, discover runners/tests, and then +/// compile everything and run the combinatorial matrix of runners against +/// tests. It's expected that each `runner.*` and `test.*` perform the same +/// functionality and only differ in source language. +#[derive(Default, Debug, Clone, Parser)] +pub struct Opts { + /// Directory containing the test being run or all tests being run. + test: Vec, + + /// Path to where binary artifacts for tests are stored. + #[clap(long, value_name = "PATH")] + artifacts: PathBuf, + + /// Optional filter to use on test names to only run some tests. + /// + /// This is a regular expression defined by the `regex` Rust crate. + #[clap(short, long, value_name = "REGEX")] + filter: Option, + + /// The executable or script used to execute a fully composed test case. + #[clap(long, default_value = "wasmtime")] + runner: std::ffi::OsString, + + #[clap(flatten)] + rust: rust::RustOpts, + + #[clap(flatten)] + c: c::COpts, + + #[clap(flatten)] + custom: custom::CustomOpts, + + /// Whether or not the calling process's stderr is inherited into child + /// processes. + /// + /// This helps preserving color in compiler error messages but can also + /// jumble up output if there are multiple errors. + #[clap(short, long)] + inherit_stderr: bool, + + /// Configuration of which languages are tested. + /// + /// Passing `--lang rust` will only test Rust for example. Passing + /// `--lang=-rust` will test everything except Rust. + #[clap(short, long)] + languages: Vec, +} + +impl Opts { + pub fn run(&self, wit_bindgen: &Path) -> Result<()> { + Runner { + opts: self, + rust_state: None, + wit_bindgen, + test_runner: runner::TestRunner::new(&self.runner)?, + } + .run() + } +} + +/// Helper structure representing a discovered `test.wit` file. +struct Test { + /// The name of this test, unique amongst all tests. + /// + /// Inferred from the directory name. + name: String, + + kind: TestKind, +} + +enum TestKind { + Runtime(Vec), + Codegen(PathBuf), +} + +/// Helper structure representing a single component found in a test directory. +struct Component { + /// The name of this component, inferred from the file stem. + /// + /// May be shared across different languages. + name: String, + + /// The path to the source file for this component. + path: PathBuf, + + /// Whether or not this component is a "runner" or a "test" + kind: Kind, + + /// The detected language for this component. + language: Language, + + /// The WIT world that's being used with this component, loaded from + /// `test.wit`. + bindgen: Bindgen, +} + +#[derive(Clone)] +struct Bindgen { + /// The arguments to the bindings generator that this component will be + /// using. + args: Vec, + /// The path to the `*.wit` file or files that are having bindings + /// generated. + wit_path: PathBuf, + /// The name of the world within `wit_path` that's having bindings generated + /// for it. + world: String, + /// Configuration found in `wit_path` + wit_config: config::WitConfig, +} + +#[derive(PartialEq)] +enum Kind { + Runner, + Test, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum Language { + Rust, + C, + Cpp, + Wat, + Custom(custom::Language), +} + +/// Helper structure to package up arguments when sent to language-specific +/// compilation backends for `LanguageMethods::compile` +struct Compile<'a> { + component: &'a Component, + bindings_dir: &'a Path, + artifacts_dir: &'a Path, + output: &'a Path, +} + +/// Helper structure to package up arguments when sent to language-specific +/// compilation backends for `LanguageMethods::verify` +struct Verify<'a> { + wit_test: &'a Path, + bindings_dir: &'a Path, + artifacts_dir: &'a Path, + args: &'a [String], + world: &'a str, +} + +/// Helper structure to package up runtime state associated with executing tests. +struct Runner<'a> { + opts: &'a Opts, + rust_state: Option, + wit_bindgen: &'a Path, + test_runner: runner::TestRunner, +} + +impl Runner<'_> { + /// Executes all tests. + fn run(&mut self) -> Result<()> { + // First step, discover all tests in the specified test directory. + let mut tests = HashMap::new(); + for test in self.opts.test.iter() { + self.discover_tests(&mut tests, test) + .with_context(|| format!("failed to discover tests in {test:?}"))?; + } + if tests.is_empty() { + bail!( + "no `test.wit` files found were found in {:?}", + self.opts.test, + ); + } + + self.prepare_languages(&tests)?; + self.run_codegen_tests(&tests)?; + self.run_runtime_tests(&tests)?; + + println!("PASSED"); + + Ok(()) + } + + /// Walks over `dir`, recursively, inserting located cases into `tests`. + fn discover_tests(&self, tests: &mut HashMap, path: &Path) -> Result<()> { + if path.is_file() { + if path.extension().and_then(|s| s.to_str()) == Some("wit") { + return self.insert_test(&path, TestKind::Codegen(path.to_owned()), tests); + } + + return Ok(()); + } + + let runtime_candidate = path.join("test.wit"); + if runtime_candidate.is_file() { + let components = self + .load_test(&runtime_candidate, path) + .with_context(|| format!("failed to load test in {path:?}"))?; + return self.insert_test(path, TestKind::Runtime(components), tests); + } + + let codegen_candidate = path.join("wit"); + if codegen_candidate.is_dir() { + return self.insert_test(path, TestKind::Codegen(codegen_candidate), tests); + } + + for entry in path.read_dir().context("failed to read test directory")? { + let entry = entry.context("failed to read test directory entry")?; + let path = entry.path(); + + self.discover_tests(tests, &path)?; + } + + Ok(()) + } + + fn insert_test( + &self, + path: &Path, + kind: TestKind, + tests: &mut HashMap, + ) -> Result<()> { + let test_name = path + .file_name() + .and_then(|s| s.to_str()) + .context("non-utf-8 filename")?; + let prev = tests.insert( + test_name.to_string(), + Test { + name: test_name.to_string(), + kind, + }, + ); + if prev.is_some() { + bail!("duplicate test name `{test_name}` found"); + } + Ok(()) + } + + /// Loads a test from `dir` using the `wit` file in the directory specified. + /// + /// Returns a list of components that were found within this directory. + fn load_test(&self, wit: &Path, dir: &Path) -> Result> { + let mut resolve = wit_parser::Resolve::default(); + let pkg = resolve + .push_file(&wit) + .context("failed to load `test.wit` in test directory")?; + let resolve = Arc::new(resolve); + resolve + .select_world(pkg, Some("runner")) + .context("failed to find expected `runner` world to generate bindings")?; + resolve + .select_world(pkg, Some("test")) + .context("failed to find expected `test` world to generate bindings")?; + + let wit_contents = std::fs::read_to_string(wit)?; + let wit_config: config::WitConfig = config::parse_test_config(&wit_contents, "//@") + .context("failed to parse WIT test config")?; + + let mut components = Vec::new(); + let mut any_runner = false; + let mut any_test = false; + + for entry in dir.read_dir().context("failed to read test directory")? { + let entry = entry.context("failed to read test directory entry")?; + let path = entry.path(); + + let Some(name) = path.file_name().and_then(|s| s.to_str()) else { + continue; + }; + let kind = if name.starts_with("runner") { + any_runner = true; + Kind::Runner + } else if name != "test.wit" && name.starts_with("test") { + any_test = true; + Kind::Test + } else { + log::debug!("skipping file {name:?}"); + continue; + }; + + let bindgen = Bindgen { + args: Vec::new(), + wit_config: wit_config.clone(), + world: kind.to_string(), + wit_path: wit.to_path_buf(), + }; + + let component = self + .parse_component(&path, kind, bindgen) + .with_context(|| format!("failed to parse component source file {path:?}"))?; + components.push(component); + } + + if !any_runner { + bail!("no `runner*` test files found in test directory"); + } + if !any_test { + bail!("no `test*` test files found in test directory"); + } + + Ok(components) + } + + /// Parsers the component located at `path` and creates all information + /// necessary for a `Component` return value. + fn parse_component(&self, path: &Path, kind: Kind, mut bindgen: Bindgen) -> Result { + let extension = path + .extension() + .and_then(|s| s.to_str()) + .context("non-utf-8 path extension")?; + + let language = match extension { + "rs" => Language::Rust, + "c" => Language::C, + "cpp" => Language::Cpp, + "wat" => Language::Wat, + other => Language::Custom(custom::Language::lookup(self, other)?), + }; + + let contents = fs::read_to_string(&path)?; + let config = match language.obj().comment_prefix_for_test_config() { + Some(comment) => { + config::parse_test_config::(&contents, comment)? + } + None => Default::default(), + }; + assert!(bindgen.args.is_empty()); + bindgen.args = config.args.into(); + + Ok(Component { + name: path.file_stem().unwrap().to_str().unwrap().to_string(), + path: path.to_path_buf(), + language, + bindgen, + kind, + }) + } + + /// Prepares all languages in use in `test` as part of a one-time + /// initialization step. + fn prepare_languages(&mut self, tests: &HashMap) -> Result<()> { + let all_languages = self.all_languages(); + + let mut prepared = HashSet::new(); + let mut prepare = |lang: &Language| -> Result<()> { + if !self.include_language(lang) || !prepared.insert(lang.clone()) { + return Ok(()); + } + lang.obj() + .prepare(self) + .with_context(|| format!("failed to prepare language {lang}")) + }; + + for test in tests.values() { + match &test.kind { + TestKind::Runtime(c) => { + for component in c { + prepare(&component.language)? + } + } + TestKind::Codegen(_) => { + for lang in all_languages.iter() { + prepare(lang)?; + } + } + } + } + + Ok(()) + } + + fn all_languages(&self) -> Vec { + let mut languages = Language::ALL.to_vec(); + for (ext, _) in self.opts.custom.custom.iter() { + languages.push(Language::Custom( + custom::Language::lookup(self, ext).unwrap(), + )); + } + languages + } + + /// Executes all tests that are `TestKind::Codegen`. + fn run_codegen_tests(&mut self, tests: &HashMap) -> Result<()> { + let mut codegen_tests = Vec::new(); + let languages = self.all_languages(); + for (name, test) in tests.iter().filter_map(|(name, t)| match &t.kind { + TestKind::Runtime(_) => None, + TestKind::Codegen(p) => Some((name, p)), + }) { + let config = match fs::read_to_string(test) { + Ok(wit) => config::parse_test_config::(&wit, "//@") + .with_context(|| format!("failed to parse test config from {test:?}"))?, + Err(_) => Default::default(), + }; + for language in languages.iter() { + // Right now C++'s generator is the same as C's, so don't + // duplicate everything there. + if *language == Language::Cpp { + continue; + } + + // If the CLI arguments filter out this language, then discard + // the test case. + if !self.include_language(&language) { + continue; + } + + codegen_tests.push(( + language.clone(), + test, + name.to_string(), + Vec::new(), + config.clone(), + )); + + for (args_kind, args) in language.obj().codegen_test_variants() { + codegen_tests.push(( + language.clone(), + test, + format!("{name}-{args_kind}"), + args.iter().map(|s| s.to_string()).collect::>(), + config.clone(), + )); + } + } + } + + if codegen_tests.is_empty() { + return Ok(()); + } + + println!("Running {} codegen tests:", codegen_tests.len()); + + let results = codegen_tests + .par_iter() + .map(|(language, test, args_kind, args, config)| { + let should_fail = language.obj().should_fail_verify(args_kind, config, args); + let result = self + .codegen_test(language, test, &args_kind, args, config) + .with_context(|| { + format!("failed to codegen test for `{language}` over {test:?}") + }); + self.update_status(&result, should_fail); + (result, should_fail, language, test, args_kind) + }) + .collect::>(); + + println!(""); + + self.render_errors(results.into_iter().map( + |(result, should_fail, language, test, args_kind)| { + StepResult::new(test.to_str().unwrap(), result) + .should_fail(should_fail) + .metadata("language", language) + .metadata("variant", args_kind) + }, + )); + + Ok(()) + } + + /// Runs a single codegen test. + /// + /// This will generate bindings for `test` in the `language` specified. The + /// test name is mangled by `args_kind` and the `args` are arguments to pass + /// to the bindings generator. + fn codegen_test( + &self, + language: &Language, + test: &Path, + args_kind: &str, + args: &[String], + config: &config::WitConfig, + ) -> Result<()> { + let mut resolve = wit_parser::Resolve::default(); + let (pkg, _) = resolve.push_path(test).context("failed to load WIT")?; + let world = resolve + .select_world(pkg, None) + .or_else(|err| resolve.select_world(pkg, Some("imports")).map_err(|_| err)) + .context("failed to select a world for bindings generation")?; + let world = resolve.worlds[world].name.clone(); + + let artifacts_dir = std::env::current_dir()? + .join(&self.opts.artifacts) + .join("codegen") + .join(language.to_string()) + .join(args_kind); + let bindings_dir = artifacts_dir.join("bindings"); + let bindgen = Bindgen { + args: args.to_vec(), + wit_path: test.to_path_buf(), + world: world.clone(), + wit_config: config.clone(), + }; + language + .obj() + .generate_bindings(self, &bindgen, &bindings_dir) + .context("failed to generate bindings")?; + + language + .obj() + .verify( + self, + &Verify { + world: &world, + artifacts_dir: &artifacts_dir, + bindings_dir: &bindings_dir, + wit_test: test, + args: &bindgen.args, + }, + ) + .context("failed to verify generated bindings")?; + + Ok(()) + } + + /// Execute all `TestKind::Runtime` tests + fn run_runtime_tests(&mut self, tests: &HashMap) -> Result<()> { + let components = tests + .values() + .filter(|t| match &self.opts.filter { + Some(filter) => filter.is_match(&t.name), + None => true, + }) + .filter_map(|t| match &t.kind { + TestKind::Runtime(c) => Some(c.iter().map(move |c| (t, c))), + TestKind::Codegen(_) => None, + }) + .flat_map(|i| i) + // Discard components that are unrelated to the languages being + // tested. + .filter(|(_test, component)| self.include_language(&component.language)) + .collect::>(); + + println!("Compiling {} components:", components.len()); + + // In parallel compile all sources to their binary component + // form. + let compile_results = components + .par_iter() + .map(|(test, component)| { + let path = self + .compile_component(test, component) + .with_context(|| format!("failed to compile component {:?}", component.path)); + self.update_status(&path, false); + (test, component, path) + }) + .collect::>(); + println!(""); + + let mut compilations = Vec::new(); + self.render_errors( + compile_results + .into_iter() + .map(|(test, component, result)| match result { + Ok(path) => { + compilations.push((test, component, path)); + StepResult::new("", Ok(())) + } + Err(e) => StepResult::new(&test.name, Err(e)) + .metadata("component", &component.name) + .metadata("path", component.path.display()), + }), + ); + + // Next, massage the data a bit. Create a map of all tests to where + // their components are located. Then perform a product of runners/tests + // to generate a list of test cases. Finally actually execute the testj + // cases. + let mut compiled_components = HashMap::new(); + for (test, component, path) in compilations { + let list = compiled_components.entry(&test.name).or_insert(Vec::new()); + list.push((component, path)); + } + + let mut to_run = Vec::new(); + for (test, components) in compiled_components.iter() { + for a in components.iter().filter(|(c, _)| c.kind == Kind::Runner) { + for b in components.iter().filter(|(c, _)| c.kind == Kind::Test) { + to_run.push((test, a, b)); + } + } + } + + println!("Running {} runtime tests:", to_run.len()); + + let results = to_run + .par_iter() + .map(|(case_name, (runner, runner_path), (test, test_path))| { + let case = &tests[case_name.as_str()]; + let result = self + .runtime_test(case, runner, runner_path, test, test_path) + .with_context(|| { + format!( + "failed to run `{}` with runner `{}` and test `{}`", + case.name, runner.language, test.language, + ) + }); + self.update_status(&result, false); + (result, case_name, runner, runner_path, test, test_path) + }) + .collect::>(); + + println!(""); + + self.render_errors(results.into_iter().map( + |(result, case_name, runner, runner_path, test, test_path)| { + StepResult::new(case_name, result) + .metadata("runner", runner.path.display()) + .metadata("test", test.path.display()) + .metadata("compiled runner", runner_path.display()) + .metadata("compiled test", test_path.display()) + }, + )); + + Ok(()) + } + + /// Compiles the `component` specified to wasm for the `test` given. + /// + /// This will generate bindings for `component` and then perform + /// language-specific compilation to convert the files into a component. + fn compile_component(&self, test: &Test, component: &Component) -> Result { + let root_dir = std::env::current_dir()? + .join(&self.opts.artifacts) + .join(&test.name); + let artifacts_dir = root_dir.join(format!("{}-{}", component.name, component.language)); + let bindings_dir = artifacts_dir.join("bindings"); + let output = root_dir.join(format!("{}-{}.wasm", component.name, component.language)); + component + .language + .obj() + .generate_bindings(self, &component.bindgen, &bindings_dir)?; + let result = Compile { + component, + bindings_dir: &bindings_dir, + artifacts_dir: &artifacts_dir, + output: &output, + }; + component.language.obj().compile(self, &result)?; + + // Double-check the output is indeed a component and it's indeed valid. + let wasm = fs::read(&output) + .with_context(|| format!("failed to read output wasm file {output:?}"))?; + if !wasmparser::Parser::is_component(&wasm) { + bail!("output file {output:?} is not a component"); + } + wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all()) + .validate_all(&wasm) + .with_context(|| format!("compiler produced invalid wasm file {output:?}"))?; + + Ok(output) + } + + /// Executes a single test case. + /// + /// Composes `runner_wasm` with `test_wasm` and then executes it with the + /// runner specified in CLIflags. + fn runtime_test( + &self, + case: &Test, + runner: &Component, + runner_wasm: &Path, + test: &Component, + test_wasm: &Path, + ) -> Result<()> { + let mut config = wasm_compose::config::Config::default(); + config.definitions = vec![test_wasm.to_path_buf()]; + let composed = wasm_compose::composer::ComponentComposer::new(runner_wasm, &config) + .compose() + .with_context(|| format!("failed to compose {runner_wasm:?} with {test_wasm:?}"))?; + let dst = runner_wasm.parent().unwrap(); + let composed_wasm = dst.join(format!( + "{}-composed-{}-{}-{}-{}.wasm", + case.name, runner.name, runner.language, test.name, test.language + )); + write_if_different(&composed_wasm, &composed)?; + + self.run_command(self.test_runner.command().arg(&composed_wasm))?; + Ok(()) + } + + /// Helper to execute an external process and generate a helpful error + /// message on failure. + fn run_command(&self, cmd: &mut Command) -> Result<()> { + if self.opts.inherit_stderr { + cmd.stderr(Stdio::inherit()); + } + let output = cmd + .output() + .with_context(|| format!("failed to spawn {cmd:?}"))?; + if output.status.success() { + return Ok(()); + } + + let mut error = format!( + "\ +command execution failed +command: {cmd:?} +status: {}", + output.status, + ); + + if !output.stdout.is_empty() { + error.push_str(&format!( + "\nstdout:\n {}", + String::from_utf8_lossy(&output.stdout).replace("\n", "\n ") + )); + } + if !output.stderr.is_empty() { + error.push_str(&format!( + "\nstderr:\n {}", + String::from_utf8_lossy(&output.stderr).replace("\n", "\n ") + )); + } + + bail!("{error}") + } + + /// Converts the WASIp1 module at `p1` to a component using the information + /// stored within `compile`. + /// + /// Stores the output at `compile.output`. + fn convert_p1_to_component(&self, p1: &Path, compile: &Compile<'_>) -> Result<()> { + let mut resolve = wit_parser::Resolve::default(); + let (pkg, _) = resolve.push_path(&compile.component.bindgen.wit_path)?; + let world = resolve.select_world(pkg, Some(&compile.component.kind.to_string()))?; + let mut module = fs::read(&p1).context("failed to read wasm file")?; + let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?; + + let section = wasm_encoder::CustomSection { + name: Cow::Borrowed("component-type"), + data: Cow::Borrowed(&encoded), + }; + module.push(section.id()); + section.encode(&mut module); + + let wasi_adapter = match compile.component.kind { + Kind::Runner => { + wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_COMMAND_ADAPTER + } + Kind::Test => { + wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER + } + }; + + let component = ComponentEncoder::default() + .module(module.as_slice()) + .context("failed to load custom sections from input module")? + .validate(true) + .adapter("wasi_snapshot_preview1", wasi_adapter) + .context("failed to load wasip1 adapter")? + .encode() + .context("failed to convert to a component")?; + write_if_different(compile.output, component)?; + Ok(()) + } + + /// "poor man's test output progress" + fn update_status(&self, result: &Result, should_fail: bool) { + if result.is_ok() == !should_fail { + print!("."); + } else { + print!("F"); + } + let _ = std::io::stdout().flush(); + } + + /// Returns whether `languages` is included in this testing session. + fn include_language(&self, language: &Language) -> bool { + let lang = language.obj().display(); + let mut any_positive = false; + let mut any_negative = false; + for opt in self.opts.languages.iter() { + for name in opt.split(',') { + if let Some(suffix) = name.strip_prefix('-') { + any_negative = true; + // If explicitly asked to not include this, don't include + // it. + if suffix == lang { + return false; + } + } else { + any_positive = true; + // If explicitly asked to include this, then include it. + if name == lang { + return true; + } + } + } + } + + // By default include all languages. + if self.opts.languages.is_empty() { + return true; + } + + // If any language was explicitly included then assume any non-mentioned + // language should be omitted. + if any_positive { + return false; + } + + // And if there are only negative mentions (e.g. `-foo`) then assume + // everything else is allowed. + assert!(any_negative); + true + } + + fn render_errors<'a>(&self, results: impl Iterator>) { + let mut failures = 0; + for result in results { + let err = match (result.result, result.should_fail) { + (Ok(()), false) | (Err(_), true) => continue, + (Err(e), false) => e, + (Ok(()), true) => anyhow!("test should have failed, but passed"), + }; + failures += 1; + + println!("------ Failure: {} --------", result.name); + for (k, v) in result.metadata { + println!(" {k}: {v}"); + } + println!(" error: {}", format!("{err:?}").replace("\n", "\n ")); + } + + if failures > 0 { + println!("{failures} tests FAILED"); + std::process::exit(1); + } + } +} + +struct StepResult<'a> { + result: Result<()>, + should_fail: bool, + name: &'a str, + metadata: Vec<(&'a str, String)>, +} + +impl<'a> StepResult<'a> { + fn new(name: &'a str, result: Result<()>) -> StepResult<'a> { + StepResult { + name, + result, + should_fail: false, + metadata: Vec::new(), + } + } + + fn should_fail(mut self, fail: bool) -> Self { + self.should_fail = fail; + self + } + + fn metadata(mut self, name: &'a str, value: impl fmt::Display) -> Self { + self.metadata.push((name, value.to_string())); + self + } +} + +/// Helper trait for each language to implement which encapsulates +/// language-specific logic. +trait LanguageMethods { + /// Display name for this language, used in filenames. + fn display(&self) -> &str; + + /// Returns the prefix that this language uses to annotate configuration in + /// the top of source files. + /// + /// This should be the language's line-comment syntax followed by `@`, e.g. + /// `//@` for Rust or `;;@` for WebAssembly Text. + fn comment_prefix_for_test_config(&self) -> Option<&str>; + + /// Returns the extra permutations, if any, of arguments to use with codegen + /// tests. + /// + /// This is used to run all codegen tests with a variety of bindings + /// generator options. The first element in the tuple is a descriptive + /// string that should be unique (used in file names) and the second elemtn + /// is the list of arguments for that variant to pass to the bindings + /// generator. + fn codegen_test_variants(&self) -> &[(&str, &[&str])] { + &[] + } + + /// Performs any one-time preparation necessary for this language, such as + /// downloading or caching dependencies. + fn prepare(&self, runner: &mut Runner<'_>) -> Result<()>; + + /// Generates bindings for `component` into `dir`. + /// + /// Runs `wit-bindgen` in aa subprocess to catch failures such as panics. + fn generate_bindings(&self, runner: &Runner<'_>, bindgen: &Bindgen, dir: &Path) -> Result<()> { + let name = match self.bindgen_name() { + Some(name) => name, + None => return Ok(()), + }; + let mut cmd = Command::new(runner.wit_bindgen); + cmd.arg(name) + .arg(&bindgen.wit_path) + .arg("--world") + .arg(format!("%{}", bindgen.world)) + .arg("--out-dir") + .arg(dir); + + match bindgen.wit_config.default_bindgen_args { + Some(true) | None => { + for arg in self.default_bindgen_args() { + cmd.arg(arg); + } + } + Some(false) => {} + } + + for arg in bindgen.args.iter() { + cmd.arg(arg); + } + + runner.run_command(&mut cmd) + } + + /// Returns the default set of arguments that will be passed to + /// `wit-bindgen`. + /// + /// Defaults to empty, but each language can override it. + fn default_bindgen_args(&self) -> &[&str] { + &[] + } + + /// Returns the name of this bindings generator when passed to + /// `wit-bindgen`. + /// + /// By default this is `Some(self.display())`, but it can be overridden if + /// necessary. Returning `None` here means that no bindings generator is + /// supported. + fn bindgen_name(&self) -> Option<&str> { + Some(self.display()) + } + + /// Performs compilation as specified by `compile`. + fn compile(&self, runner: &Runner<'_>, compile: &Compile) -> Result<()>; + + /// Returns whether this language is supposed to fail this codegen tests + /// given the `config` and `args` for the test. + fn should_fail_verify(&self, name: &str, config: &config::WitConfig, args: &[String]) -> bool; + + /// Performs a "check" or a verify that the generated bindings described by + /// `Verify` are indeed valid. + fn verify(&self, runner: &Runner<'_>, verify: &Verify) -> Result<()>; +} + +impl Language { + const ALL: &[Language] = &[Language::Rust, Language::C, Language::Cpp, Language::Wat]; + + fn obj(&self) -> &dyn LanguageMethods { + match self { + Language::Rust => &rust::Rust, + Language::C => &c::C, + Language::Cpp => &c::Cpp, + Language::Wat => &wat::Wat, + Language::Custom(custom) => custom, + } + } +} + +impl fmt::Display for Language { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.obj().display().fmt(f) + } +} + +impl fmt::Display for Kind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Kind::Runner => "runner".fmt(f), + Kind::Test => "test".fmt(f), + } + } +} + +/// Returns `true` if the file was written, or `false` if the file is the same +/// as it was already on disk. +fn write_if_different(path: &Path, contents: impl AsRef<[u8]>) -> Result { + let contents = contents.as_ref(); + if let Ok(prev) = fs::read(path) { + if prev == contents { + return Ok(false); + } + } + + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .with_context(|| format!("failed to create directory {parent:?}"))?; + } + fs::write(path, contents).with_context(|| format!("failed to write {path:?}"))?; + Ok(true) +} diff --git a/crates/test/src/runner.rs b/crates/test/src/runner.rs new file mode 100644 index 000000000..fe5c48ac4 --- /dev/null +++ b/crates/test/src/runner.rs @@ -0,0 +1,50 @@ +use anyhow::{Context, Result}; +use std::ffi::OsString; +use std::process::Command; + +/// Helper structure representing a test runner, which is a program argument +/// followed by a number of optional arguments. +pub struct TestRunner { + path: OsString, + args: Vec, +} + +impl TestRunner { + pub fn new(runner: &OsString) -> Result { + // First see if `runner` itself is a valid executable, if so use it as-is. + let original_err = match Command::new(runner).arg("--version").output() { + Ok(_) => { + return Ok(TestRunner { + path: runner.clone(), + args: Vec::new(), + }) + } + Err(e) => e, + }; + + // Failing that see if `runner` looks like `foo --bar --baz` where + // space-delimited arguments are used. + let runner_and_args = runner.to_str().context("--runner argument is not utf-8")?; + let mut delimited = runner_and_args.split_whitespace(); + let command = delimited.next().unwrap(); + if Command::new(command).arg("--version").output().is_ok() { + return Ok(TestRunner { + path: command.into(), + args: delimited.map(|s| s.to_string()).collect(), + }); + } + + // Failing that return an error. It's left as a future extension to + // support arguemnts-with-spaces or runtimes-with-spaces. + Err(original_err).context(format!("runner `{runner_and_args}` failed to spawn")) + } + + /// Returns a `Command` which can be used to execute this test runner. + pub fn command(&self) -> Command { + let mut ret = Command::new(&self.path); + for arg in self.args.iter() { + ret.arg(arg); + } + ret + } +} diff --git a/crates/test/src/rust.rs b/crates/test/src/rust.rs new file mode 100644 index 000000000..b22959ca4 --- /dev/null +++ b/crates/test/src/rust.rs @@ -0,0 +1,252 @@ +use crate::{Compile, Kind, LanguageMethods, Runner, Verify}; +use anyhow::Result; +use clap::Parser; +use heck::ToSnakeCase; +use std::env; +use std::path::PathBuf; +use std::process::Command; + +#[derive(Default, Debug, Clone, Parser)] +pub struct RustOpts { + /// A custom `path` dependency to use for `wit-bindgen`. + #[clap(long, conflicts_with = "rust_wit_bindgen_version", value_name = "PATH")] + rust_wit_bindgen_path: Option, + + /// A custom version to use for the `wit-bindgen` dependency. + #[clap(long, conflicts_with = "rust_wit_bindgen_path", value_name = "X.Y.Z")] + rust_wit_bindgen_version: Option, + + /// A custom version to use for the `wit-bindgen` dependency. + #[clap(long, default_value = "wasm32-wasip2", value_name = "TARGET")] + rust_target: String, +} + +pub struct Rust; + +#[derive(Default)] +pub struct State { + wit_bindgen_rlib: PathBuf, + futures_rlib: PathBuf, + wit_bindgen_deps: Vec, +} + +impl LanguageMethods for Rust { + fn display(&self) -> &str { + "rust" + } + + fn comment_prefix_for_test_config(&self) -> Option<&str> { + Some("//@") + } + + fn should_fail_verify( + &self, + name: &str, + config: &crate::config::WitConfig, + args: &[String], + ) -> bool { + // no_std doesn't currently work with async + if config.async_ && args.iter().any(|s| s == "--std-feature") { + return true; + } + + // Currently there's a bug with this borrowing mode which means that + // this variant does not pass. + if name == "wasi-http-borrowed-duplicate" { + return true; + } + + false + } + + fn codegen_test_variants(&self) -> &[(&str, &[&str])] { + &[ + ("borrowed", &["--ownership=borrowing"]), + ( + "borrowed-duplicate", + &["--ownership=borrowing-duplicate-if-necessary"], + ), + ("async", &["--async=all"]), + ("no-std", &["--std-feature"]), + ] + } + + fn default_bindgen_args(&self) -> &[&str] { + &["--generate-all"] + } + + fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + let cwd = env::current_dir()?; + let opts = &runner.opts.rust; + let dir = cwd.join(&runner.opts.artifacts).join("rust"); + let wit_bindgen = dir.join("wit-bindgen"); + + let wit_bindgen_dep = match &opts.rust_wit_bindgen_path { + Some(path) => format!("path = {:?}", cwd.join(path)), + None => { + let version = opts + .rust_wit_bindgen_version + .as_deref() + .unwrap_or(env!("CARGO_PKG_VERSION")); + format!("version = \"{version}\"") + } + }; + + super::write_if_different( + &wit_bindgen.join("Cargo.toml"), + &format!( + r#" +[package] +name = "tmp" + +[workspace] + +[dependencies] +wit-bindgen = {{ {wit_bindgen_dep} }} +futures = "0.3.31" + +[lib] +path = 'lib.rs' + "#, + ), + )?; + super::write_if_different(&wit_bindgen.join("lib.rs"), "")?; + + println!("Building `wit-bindgen` from crates.io..."); + runner.run_command( + Command::new("cargo") + .current_dir(&wit_bindgen) + .arg("build") + .arg("-pwit-bindgen") + .arg("-pfutures") + .arg("--target") + .arg(&opts.rust_target), + )?; + + let target_out_dir = wit_bindgen + .join("target") + .join(&opts.rust_target) + .join("debug"); + let host_out_dir = wit_bindgen.join("target/debug"); + let wit_bindgen_rlib = target_out_dir.join("libwit_bindgen.rlib"); + let futures_rlib = target_out_dir.join("libfutures.rlib"); + assert!(wit_bindgen_rlib.exists()); + assert!(futures_rlib.exists()); + + runner.rust_state = Some(State { + wit_bindgen_rlib, + futures_rlib, + wit_bindgen_deps: vec![target_out_dir.join("deps"), host_out_dir.join("deps")], + }); + Ok(()) + } + + fn compile(&self, runner: &Runner<'_>, compile: &Compile) -> Result<()> { + let mut cmd = runner.rustc(); + + // If this rust target doesn't natively produce a component then place + // the compiler output in a temporary location which is componentized + // later on. + let output = if runner.produces_component() { + compile.output.to_path_buf() + } else { + compile.output.with_extension("core.wasm") + }; + + cmd.current_dir(compile.component.path.parent().unwrap()) + .env("CARGO_MANIFEST_DIR", ".") + .env( + "BINDINGS", + compile + .bindings_dir + .join(format!("{}.rs", compile.component.kind)), + ) + .arg(compile.component.path.file_name().unwrap()) + .arg("-Dwarnings") + .arg("-o") + .arg(&output); + match compile.component.kind { + Kind::Runner => {} + Kind::Test => { + cmd.arg("--crate-type=cdylib"); + } + } + runner.run_command(&mut cmd)?; + + if !runner.produces_component() { + runner.convert_p1_to_component(&output, compile)?; + } + + Ok(()) + } + + fn verify(&self, runner: &Runner<'_>, verify: &Verify<'_>) -> Result<()> { + let mut cmd = runner.rustc(); + let bindings = verify + .bindings_dir + .join(format!("{}.rs", verify.world.to_snake_case())); + cmd.arg(&bindings) + .arg("--crate-type=rlib") + .arg("-o") + .arg(verify.artifacts_dir.join("tmp")); + runner.run_command(&mut cmd)?; + + // If bindings are generated in `#![no_std]` mode then verify that it + // compiles as such. + if verify.args.iter().any(|s| s == "--std-feature") { + let no_std_root = verify.artifacts_dir.join("no_std.rs"); + super::write_if_different( + &no_std_root, + r#" +#![no_std] +include!(env!("BINDINGS")); + +// This empty module named 'core' is here to catch module path +// conflicts with 'core' modules used in code generated by the +// wit_bindgen::generate macro. +// Ref: https://github.com/bytecodealliance/wit-bindgen/pull/568 +mod core {} + "#, + )?; + let mut cmd = runner.rustc(); + cmd.arg(&no_std_root) + .env("BINDINGS", &bindings) + .arg("--crate-type=rlib") + .arg("-o") + .arg(verify.artifacts_dir.join("tmp")); + runner.run_command(&mut cmd)?; + } + Ok(()) + } +} + +impl Runner<'_> { + fn rustc(&self) -> Command { + let state = self.rust_state.as_ref().unwrap(); + let opts = &self.opts.rust; + let mut cmd = Command::new("rustc"); + cmd.arg("--edition=2021") + .arg(&format!( + "--extern=wit_bindgen={}", + state.wit_bindgen_rlib.display() + )) + .arg(&format!( + "--extern=futures={}", + state.futures_rlib.display() + )) + .arg("--target") + .arg(&opts.rust_target) + .arg("-Cdebuginfo=1"); + for dep in state.wit_bindgen_deps.iter() { + cmd.arg(&format!("-Ldependency={}", dep.display())); + } + cmd + } + + fn produces_component(&self) -> bool { + match self.opts.rust.rust_target.as_str() { + "wasm32-unknown-unknown" | "wasm32-wasi" | "wasm32-wasip1" => false, + _ => true, + } + } +} diff --git a/crates/test/src/wat.rs b/crates/test/src/wat.rs new file mode 100644 index 000000000..6209000c2 --- /dev/null +++ b/crates/test/src/wat.rs @@ -0,0 +1,49 @@ +use crate::{Compile, LanguageMethods, Runner, Verify}; +use anyhow::Result; + +pub struct Wat; + +impl LanguageMethods for Wat { + fn display(&self) -> &str { + "wat" + } + + fn bindgen_name(&self) -> Option<&str> { + None + } + + fn should_fail_verify( + &self, + _name: &str, + _config: &crate::config::WitConfig, + _args: &[String], + ) -> bool { + false + } + + fn comment_prefix_for_test_config(&self) -> Option<&str> { + Some(";;@") + } + + fn prepare(&self, _runner: &mut Runner<'_>) -> Result<()> { + Ok(()) + } + + fn compile(&self, runner: &Runner<'_>, compile: &Compile<'_>) -> Result<()> { + let wasm = wat::parse_file(&compile.component.path)?; + if wasmparser::Parser::is_component(&wasm) { + super::write_if_different(&compile.output, wasm)?; + return Ok(()); + } + + let p1 = compile.output.with_extension("core.wasm"); + super::write_if_different(&p1, wasm)?; + runner.convert_p1_to_component(&p1, compile)?; + Ok(()) + } + + fn verify(&self, _runner: &Runner<'_>, _verify: &Verify<'_>) -> Result<()> { + // doesn't participate in codegen tests + Ok(()) + } +} diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 55afe5daa..23d9a96cd 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -62,6 +62,12 @@ enum Opt { #[clap(flatten)] args: Common, }, + + // doc-comments are present on `wit_bindgen_test::Opts` for clap to use. + Test { + #[clap(flatten)] + opts: wit_bindgen_test::Opts, + }, } #[derive(Debug, Parser)] @@ -127,6 +133,7 @@ fn main() -> Result<()> { } #[cfg(feature = "csharp")] Opt::CSharp { opts, args } => (opts.build(), args), + Opt::Test { opts } => return opts.run(std::env::args_os().nth(0).unwrap().as_ref()), }; gen_world(generator, &opt, &mut files).map_err(attach_with_context)?; diff --git a/tests/codegen/error-context.wit b/tests/codegen/error-context.wit index d76f89685..8e9e06e97 100644 --- a/tests/codegen/error-context.wit +++ b/tests/codegen/error-context.wit @@ -1,3 +1,5 @@ +//@ async = true + package foo:foo; interface error-contexts { diff --git a/tests/codegen/futures.wit b/tests/codegen/futures.wit index ff24a5098..47237eeec 100644 --- a/tests/codegen/futures.wit +++ b/tests/codegen/futures.wit @@ -1,3 +1,5 @@ +//@ async = true + package foo:foo; interface futures { diff --git a/tests/codegen/resources-with-futures.wit b/tests/codegen/resources-with-futures.wit index 33a2b2aeb..a48e1e523 100644 --- a/tests/codegen/resources-with-futures.wit +++ b/tests/codegen/resources-with-futures.wit @@ -1,3 +1,5 @@ +//@ async = true + package my:resources; interface with-futures { diff --git a/tests/codegen/resources-with-streams.wit b/tests/codegen/resources-with-streams.wit index d9e3620fc..b78cb173c 100644 --- a/tests/codegen/resources-with-streams.wit +++ b/tests/codegen/resources-with-streams.wit @@ -1,3 +1,5 @@ +//@ async = true + package my:resources; interface with-streams { diff --git a/tests/codegen/streams.wit b/tests/codegen/streams.wit index 96ccfeed2..271c8200b 100644 --- a/tests/codegen/streams.wit +++ b/tests/codegen/streams.wit @@ -1,3 +1,5 @@ +//@ async = true + package foo:foo; interface transmit { diff --git a/tests/runtime-new/c/rename/runner.c b/tests/runtime-new/c/rename/runner.c new file mode 100644 index 000000000..23d07b892 --- /dev/null +++ b/tests/runtime-new/c/rename/runner.c @@ -0,0 +1,9 @@ +//@ args = '--rename a=rename3 --rename foo:bar/b=rename4' + +#include + +int main() { + rename3_f(); + rename4_f(); + return 0; +} diff --git a/tests/runtime-new/c/rename/test.c b/tests/runtime-new/c/rename/test.c new file mode 100644 index 000000000..a90226661 --- /dev/null +++ b/tests/runtime-new/c/rename/test.c @@ -0,0 +1,7 @@ +//@ args = '--rename a=rename1 --rename foo:bar/b=rename2' + +#include + +void rename1_f() {} + +void exports_rename2_f() {} diff --git a/tests/runtime-new/c/rename/test.wit b/tests/runtime-new/c/rename/test.wit new file mode 100644 index 000000000..a46e76a1f --- /dev/null +++ b/tests/runtime-new/c/rename/test.wit @@ -0,0 +1,19 @@ +package foo:bar; + +interface b { + f: func(); +} + +world runner { + import a: interface { + f: func(); + } + import b; +} + +world test { + export a: interface { + f: func(); + } + export b; +} diff --git a/tests/runtime-new/demo/runner-component.wat b/tests/runtime-new/demo/runner-component.wat new file mode 100644 index 000000000..ae76aae80 --- /dev/null +++ b/tests/runtime-new/demo/runner-component.wat @@ -0,0 +1,23 @@ +(component + (import "a:b/the-test" (instance $test + (export "x" (func)) + )) + + (core module $m + (import "a:b/the-test" "x" (func $x)) + + (func (export "run") (result i32) + call $x + i32.const 0) + ) + (core func $x (canon lower (func $test "x"))) + (core instance $i (instantiate $m + (with "a:b/the-test" (instance + (export "x" (func $x)) + )) + )) + + (func $run (result (result)) (canon lift (core func $i "run"))) + (instance $run (export "run" (func $run))) + (export "wasi:cli/run@0.2.0" (instance $run)) +) diff --git a/tests/runtime-new/demo/runner-core.wat b/tests/runtime-new/demo/runner-core.wat new file mode 100644 index 000000000..1efe8ad3d --- /dev/null +++ b/tests/runtime-new/demo/runner-core.wat @@ -0,0 +1,9 @@ +(module + (import "a:b/the-test" "x" (func $x)) + (memory (export "memory") 1) + + (func (export "_start") + call $x + ) + +) diff --git a/tests/runtime-new/demo/runner.c b/tests/runtime-new/demo/runner.c new file mode 100644 index 000000000..5eb5722ad --- /dev/null +++ b/tests/runtime-new/demo/runner.c @@ -0,0 +1,5 @@ +#include + +int main() { + a_b_the_test_x(); +} diff --git a/tests/runtime-new/demo/runner.cpp b/tests/runtime-new/demo/runner.cpp new file mode 100644 index 000000000..5eb5722ad --- /dev/null +++ b/tests/runtime-new/demo/runner.cpp @@ -0,0 +1,5 @@ +#include + +int main() { + a_b_the_test_x(); +} diff --git a/tests/runtime-new/demo/runner.rs b/tests/runtime-new/demo/runner.rs new file mode 100644 index 000000000..2c64f7e2a --- /dev/null +++ b/tests/runtime-new/demo/runner.rs @@ -0,0 +1,5 @@ +include!(env!("BINDINGS")); + +fn main() { + a::b::the_test::x(); +} diff --git a/tests/runtime-new/demo/runner2.rs b/tests/runtime-new/demo/runner2.rs new file mode 100644 index 000000000..2c64f7e2a --- /dev/null +++ b/tests/runtime-new/demo/runner2.rs @@ -0,0 +1,5 @@ +include!(env!("BINDINGS")); + +fn main() { + a::b::the_test::x(); +} diff --git a/tests/runtime-new/demo/test-component.wat b/tests/runtime-new/demo/test-component.wat new file mode 100644 index 000000000..19d6f840a --- /dev/null +++ b/tests/runtime-new/demo/test-component.wat @@ -0,0 +1,10 @@ +(component + (core module $m + (func (export "x")) + ) + (core instance $i (instantiate $m)) + + (func $x (canon lift (core func $i "x"))) + (instance $test (export "x" (func $x))) + (export "a:b/the-test" (instance $test)) +) diff --git a/tests/runtime-new/demo/test-core.wat b/tests/runtime-new/demo/test-core.wat new file mode 100644 index 000000000..bfbc53433 --- /dev/null +++ b/tests/runtime-new/demo/test-core.wat @@ -0,0 +1,5 @@ +(module + (memory (export "memory") 1) + + (func (export "a:b/the-test#x")) +) diff --git a/tests/runtime-new/demo/test.c b/tests/runtime-new/demo/test.c new file mode 100644 index 000000000..3791c619f --- /dev/null +++ b/tests/runtime-new/demo/test.c @@ -0,0 +1,4 @@ +#include + +void exports_a_b_the_test_x() { +} diff --git a/tests/runtime-new/demo/test.cpp b/tests/runtime-new/demo/test.cpp new file mode 100644 index 000000000..3791c619f --- /dev/null +++ b/tests/runtime-new/demo/test.cpp @@ -0,0 +1,4 @@ +#include + +void exports_a_b_the_test_x() { +} diff --git a/tests/runtime-new/demo/test.rs b/tests/runtime-new/demo/test.rs new file mode 100644 index 000000000..56ba2cf3d --- /dev/null +++ b/tests/runtime-new/demo/test.rs @@ -0,0 +1,9 @@ +include!(env!("BINDINGS")); + +export!(Test); + +struct Test; + +impl exports::a::b::the_test::Guest for Test { + fn x() {} +} diff --git a/tests/runtime-new/demo/test.wit b/tests/runtime-new/demo/test.wit new file mode 100644 index 000000000..685147ec5 --- /dev/null +++ b/tests/runtime-new/demo/test.wit @@ -0,0 +1,13 @@ +package a:b; + +interface the-test { + x: func(); +} + +world runner { + import the-test; +} + +world test { + export the-test; +} diff --git a/tests/runtime-new/demo/test2.rs b/tests/runtime-new/demo/test2.rs new file mode 100644 index 000000000..56ba2cf3d --- /dev/null +++ b/tests/runtime-new/demo/test2.rs @@ -0,0 +1,9 @@ +include!(env!("BINDINGS")); + +export!(Test); + +struct Test; + +impl exports::a::b::the_test::Guest for Test { + fn x() {} +} diff --git a/tests/runtime-new/gated-features/runner.rs b/tests/runtime-new/gated-features/runner.rs new file mode 100644 index 000000000..4e023ebc6 --- /dev/null +++ b/tests/runtime-new/gated-features/runner.rs @@ -0,0 +1,10 @@ +//@ args = '--features y' + +include!(env!("BINDINGS")); + +use crate::foo::bar::bindings::{y, z}; + +fn main() { + y(); + z(); +} diff --git a/tests/runtime-new/gated-features/test.rs b/tests/runtime-new/gated-features/test.rs new file mode 100644 index 000000000..cddfb967b --- /dev/null +++ b/tests/runtime-new/gated-features/test.rs @@ -0,0 +1,14 @@ +//@ args = '--features y' + +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::foo::bar::bindings::Guest; + +impl Guest for Component { + fn y() {} + fn z() {} +} diff --git a/tests/runtime-new/gated-features/test.wit b/tests/runtime-new/gated-features/test.wit new file mode 100644 index 000000000..bdc258b61 --- /dev/null +++ b/tests/runtime-new/gated-features/test.wit @@ -0,0 +1,17 @@ +package foo:bar@1.2.3; + +interface bindings { + @unstable(feature = x) + x: func(); + @unstable(feature = y) + y: func(); + @since(version = 1.2.3) + z: func(); +} + +world test { + export bindings; +} +world runner { + import bindings; +} diff --git a/tests/runtime-new/lists-alias/runner.rs b/tests/runtime-new/lists-alias/runner.rs new file mode 100644 index 000000000..46e8be39b --- /dev/null +++ b/tests/runtime-new/lists-alias/runner.rs @@ -0,0 +1,10 @@ +include!(env!("BINDINGS")); + +fn main() { + // Test the argument is `&[u8]` + cat::foo(b"hello"); + + // Test the return type is `Vec` + let t: Vec = cat::bar(); + assert_eq!(t, b"world"); +} diff --git a/tests/runtime-new/lists-alias/test.rs b/tests/runtime-new/lists-alias/test.rs new file mode 100644 index 000000000..5d020f9ef --- /dev/null +++ b/tests/runtime-new/lists-alias/test.rs @@ -0,0 +1,15 @@ +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +impl exports::cat::Guest for Test { + fn foo(x: Vec) { + assert_eq!(x, b"hello"); + } + + fn bar() -> Vec { + b"world".into() + } +} diff --git a/tests/runtime-new/lists-alias/test.wit b/tests/runtime-new/lists-alias/test.wit new file mode 100644 index 000000000..55dbb3992 --- /dev/null +++ b/tests/runtime-new/lists-alias/test.wit @@ -0,0 +1,17 @@ +package my:lists; + +world runner { + import cat: interface { + type my-list = list; + foo: func(x: my-list); + bar: func() -> my-list; + } +} + +world test { + export cat: interface { + type my-list = list; + foo: func(x: my-list); + bar: func() -> my-list; + } +} diff --git a/tests/runtime-new/lists/alloc.rs b/tests/runtime-new/lists/alloc.rs new file mode 100644 index 000000000..0bb2559da --- /dev/null +++ b/tests/runtime-new/lists/alloc.rs @@ -0,0 +1,30 @@ +use std::alloc::{GlobalAlloc, Layout, System}; +use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + +#[global_allocator] +static ALLOC: A = A; + +static ALLOC_AMT: AtomicUsize = AtomicUsize::new(0); + +struct A; + +unsafe impl GlobalAlloc for A { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let ptr = System.alloc(layout); + if !ptr.is_null() { + ALLOC_AMT.fetch_add(layout.size(), SeqCst); + } + return ptr; + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // Poison all deallocations to try to catch any use-after-free in the + // bindings as early as possible. + std::ptr::write_bytes(ptr, 0xde, layout.size()); + ALLOC_AMT.fetch_sub(layout.size(), SeqCst); + System.dealloc(ptr, layout) + } +} +pub fn get() -> usize { + ALLOC_AMT.load(SeqCst) +} diff --git a/tests/runtime-new/lists/runner.c b/tests/runtime-new/lists/runner.c new file mode 100644 index 000000000..11ccd7123 --- /dev/null +++ b/tests/runtime-new/lists/runner.c @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "runner.h" + +int main() { + { + uint8_t list[] = {}; + runner_list_u8_t a; + a.ptr = list; + a.len = 0; + test_lists_to_test_empty_list_param(&a); + } + + { + runner_string_t a; + runner_string_set(&a, ""); + test_lists_to_test_empty_string_param(&a); + } + + { + runner_list_u8_t a; + test_lists_to_test_empty_list_result(&a); + assert(a.len == 0); + } + + { + runner_string_t a; + test_lists_to_test_empty_string_result(&a); + assert(a.len == 0); + } + + { + uint8_t list[] = {1, 2, 3, 4}; + runner_list_u8_t a; + a.ptr = list; + a.len = 4; + test_lists_to_test_list_param(&a); + } + + { + runner_string_t a; + runner_string_set(&a, "foo"); + test_lists_to_test_list_param2(&a); + } + + { + runner_string_t list[3]; + runner_string_set(&list[0], "foo"); + runner_string_set(&list[1], "bar"); + runner_string_set(&list[2], "baz"); + runner_list_string_t a; + a.ptr = list; + a.len = 3; + test_lists_to_test_list_param3(&a); + } + + { + runner_string_t list1[2]; + runner_string_t list2[1]; + runner_string_set(&list1[0], "foo"); + runner_string_set(&list1[1], "bar"); + runner_string_set(&list2[0], "baz"); + runner_list_list_string_t a; + a.ptr[0].len = 2; + a.ptr[0].ptr = list1; + a.ptr[1].len = 1; + a.ptr[1].ptr = list2; + a.len = 2; + test_lists_to_test_list_param4(&a); + } + + { + runner_tuple3_u8_u32_u8_t data[2]; + data[0].f0 = 1; + data[0].f1 = 2; + data[0].f2 = 3; + data[1].f0 = 4; + data[1].f1 = 5; + data[1].f2 = 6; + runner_list_tuple3_u8_u32_u8_t a; + a.len = 2; + a.ptr = data; + test_lists_to_test_list_param5(&a); + } + + { + runner_list_u8_t a; + test_lists_to_test_list_result(&a); + assert(a.len == 5); + assert(memcmp(a.ptr, "\x01\x02\x03\x04\x05", 5) == 0); + runner_list_u8_free(&a); + } + + { + runner_string_t a; + test_lists_to_test_list_result2(&a); + assert(a.len == 6); + assert(memcmp(a.ptr, "hello!", 6) == 0); + runner_string_free(&a); + } + + { + runner_list_string_t a; + test_lists_to_test_list_result3(&a); + assert(a.len == 2); + assert(a.ptr[0].len == 6); + assert(a.ptr[1].len == 6); + assert(memcmp(a.ptr[0].ptr, "hello,", 6) == 0); + assert(memcmp(a.ptr[1].ptr, "world!", 6) == 0); + runner_list_string_free(&a); + } + + { + runner_list_u8_t a, b; + a.len = 0; + a.ptr = (unsigned char*) ""; + test_lists_to_test_list_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + runner_list_u8_free(&b); + + a.len = 1; + a.ptr = (unsigned char*) "x"; + test_lists_to_test_list_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + runner_list_u8_free(&b); + + a.len = 5; + a.ptr = (unsigned char*) "hello"; + test_lists_to_test_list_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + runner_list_u8_free(&b); + } + + { + runner_string_t a, b; + runner_string_set(&a, "x"); + test_lists_to_test_string_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + runner_string_free(&b); + + runner_string_set(&a, ""); + test_lists_to_test_string_roundtrip(&a, &b); + assert(b.len == a.len); + runner_string_free(&b); + + runner_string_set(&a, "hello"); + test_lists_to_test_string_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + runner_string_free(&b); + + runner_string_set(&a, "hello ⚑ world"); + test_lists_to_test_string_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + runner_string_free(&b); + } + + { + uint8_t u8[2] = {0, UCHAR_MAX}; + int8_t s8[2] = {SCHAR_MIN, SCHAR_MAX}; + runner_list_u8_t list_u8 = { u8, 2 }; + runner_list_s8_t list_s8 = { s8, 2 }; + runner_tuple2_list_u8_list_s8_t ret; + test_lists_to_test_list_minmax8(&list_u8, &list_s8, &ret); + assert(ret.f0.len == 2 && ret.f0.ptr[0] == 0 && ret.f0.ptr[1] == UCHAR_MAX); + assert(ret.f1.len == 2 && ret.f1.ptr[0] == SCHAR_MIN && ret.f1.ptr[1] == SCHAR_MAX); + runner_list_u8_free(&ret.f0); + runner_list_s8_free(&ret.f1); + } + + { + uint16_t u16[2] = {0, USHRT_MAX}; + int16_t s16[2] = {SHRT_MIN, SHRT_MAX}; + runner_list_u16_t list_u16 = { u16, 2 }; + runner_list_s16_t list_s16 = { s16, 2 }; + runner_tuple2_list_u16_list_s16_t ret; + test_lists_to_test_list_minmax16(&list_u16, &list_s16, &ret); + assert(ret.f0.len == 2 && ret.f0.ptr[0] == 0 && ret.f0.ptr[1] == USHRT_MAX); + assert(ret.f1.len == 2 && ret.f1.ptr[0] == SHRT_MIN && ret.f1.ptr[1] == SHRT_MAX); + runner_list_u16_free(&ret.f0); + runner_list_s16_free(&ret.f1); + } + + { + uint32_t u32[2] = {0, UINT_MAX}; + int32_t s32[2] = {INT_MIN, INT_MAX}; + runner_list_u32_t list_u32 = { u32, 2 }; + runner_list_s32_t list_s32 = { s32, 2 }; + runner_tuple2_list_u32_list_s32_t ret; + test_lists_to_test_list_minmax32(&list_u32, &list_s32, &ret); + assert(ret.f0.len == 2 && ret.f0.ptr[0] == 0 && ret.f0.ptr[1] == UINT_MAX); + assert(ret.f1.len == 2 && ret.f1.ptr[0] == INT_MIN && ret.f1.ptr[1] == INT_MAX); + runner_list_u32_free(&ret.f0); + runner_list_s32_free(&ret.f1); + } + + { + uint64_t u64[2] = {0, ULLONG_MAX}; + int64_t s64[2] = {LLONG_MIN, LLONG_MAX}; + runner_list_u64_t list_u64 = { u64, 2 }; + runner_list_s64_t list_s64 = { s64, 2 }; + runner_tuple2_list_u64_list_s64_t ret; + test_lists_to_test_list_minmax64(&list_u64, &list_s64, &ret); + assert(ret.f0.len == 2 && ret.f0.ptr[0] == 0 && ret.f0.ptr[1] == ULLONG_MAX); + assert(ret.f1.len == 2 && ret.f1.ptr[0] == LLONG_MIN && ret.f1.ptr[1] == LLONG_MAX); + runner_list_u64_free(&ret.f0); + runner_list_s64_free(&ret.f1); + } + + { + float f32[4] = {-FLT_MAX, FLT_MAX, -INFINITY, INFINITY}; + double f64[4] = {-DBL_MAX, DBL_MAX, -INFINITY, INFINITY}; + runner_list_f32_t list_f32 = { f32, 4 }; + runner_list_f64_t list_f64 = { f64, 4 }; + runner_tuple2_list_f32_list_f64_t ret; + test_lists_to_test_list_minmax_float(&list_f32, &list_f64, &ret); + assert(ret.f0.len == 4 && ret.f0.ptr[0] == -FLT_MAX && ret.f0.ptr[1] == FLT_MAX); + assert(ret.f0.ptr[2] == -INFINITY && ret.f0.ptr[3] == INFINITY); + assert(ret.f1.len == 4 && ret.f1.ptr[0] == -DBL_MAX && ret.f1.ptr[1] == DBL_MAX); + assert(ret.f1.ptr[2] == -INFINITY && ret.f1.ptr[3] == INFINITY); + runner_list_f32_free(&ret.f0); + runner_list_f64_free(&ret.f1); + } +} diff --git a/tests/runtime/lists/wasm.rs b/tests/runtime-new/lists/runner.rs similarity index 52% rename from tests/runtime/lists/wasm.rs rename to tests/runtime-new/lists/runner.rs index 233d480ab..5d542649c 100644 --- a/tests/runtime/lists/wasm.rs +++ b/tests/runtime-new/lists/runner.rs @@ -1,65 +1,151 @@ -wit_bindgen::generate!({ - path: "../../tests/runtime/lists", -}); +include!(env!("BINDINGS")); -struct Component; +use test::lists::to_test::*; -export!(Component); +struct Guard { + me_before: usize, + remote_before: u32, +} -impl Guest for Component { - fn allocated_bytes() -> u32 { - test_rust_wasm::get() as u32 +impl Guard { + fn new() -> Guard { + Guard { + me_before: alloc::get(), + remote_before: allocated_bytes(), + } } +} + +impl Drop for Guard { + fn drop(&mut self) { + assert_eq!(self.me_before, alloc::get()); + assert_eq!(self.remote_before, allocated_bytes()); + } +} - fn test_imports() { - use test::lists::test::*; +mod alloc; - let _guard = test_rust_wasm::guard(); +fn main() { + let _guard_over_entire_function = Guard::new(); + { + let _guard = Guard::new(); empty_list_param(&[]); + } + { + let _guard = Guard::new(); empty_string_param(""); + } + { + let _guard = Guard::new(); assert!(empty_list_result().is_empty()); + } + { + let _guard = Guard::new(); assert!(empty_string_result().is_empty()); + } + { + let _guard = Guard::new(); list_param(&[1, 2, 3, 4]); + } + { + let _guard = Guard::new(); list_param2("foo"); + } + { + let _guard = Guard::new(); list_param3(&["foo".to_owned(), "bar".to_owned(), "baz".to_owned()]); + } + { + let _guard = Guard::new(); list_param4(&[ vec!["foo".to_owned(), "bar".to_owned()], vec!["baz".to_owned()], ]); + } + { + let _guard = Guard::new(); list_param5(&[(1, 2, 3), (4, 5, 6)]); + } + { + let _guard = Guard::new(); let large_list: Vec = (0..1000).map(|_| "string".to_string()).collect(); list_param_large(&large_list); + } + { + let _guard = Guard::new(); assert_eq!(list_result(), [1, 2, 3, 4, 5]); + } + { + let _guard = Guard::new(); assert_eq!(list_result2(), "hello!"); + } + { + let _guard = Guard::new(); assert_eq!(list_result3(), ["hello,", "world!"]); + } + { + let _guard = Guard::new(); assert_eq!(list_roundtrip(&[]), []); + } + { + let _guard = Guard::new(); assert_eq!(list_roundtrip(b"x"), b"x"); + } + { + let _guard = Guard::new(); assert_eq!(list_roundtrip(b"hello"), b"hello"); + } + { + let _guard = Guard::new(); assert_eq!(string_roundtrip("x"), "x"); + } + { + let _guard = Guard::new(); assert_eq!(string_roundtrip(""), ""); + } + { + let _guard = Guard::new(); assert_eq!(string_roundtrip("hello"), "hello"); + } + { + let _guard = Guard::new(); assert_eq!(string_roundtrip("hello ⚑ world"), "hello ⚑ world"); + } + { + let _guard = Guard::new(); assert_eq!( list_minmax8(&[u8::MIN, u8::MAX], &[i8::MIN, i8::MAX]), (vec![u8::MIN, u8::MAX], vec![i8::MIN, i8::MAX]), ); + } + { + let _guard = Guard::new(); assert_eq!( list_minmax16(&[u16::MIN, u16::MAX], &[i16::MIN, i16::MAX]), (vec![u16::MIN, u16::MAX], vec![i16::MIN, i16::MAX]), ); + } + { + let _guard = Guard::new(); assert_eq!( list_minmax32(&[u32::MIN, u32::MAX], &[i32::MIN, i32::MAX]), (vec![u32::MIN, u32::MAX], vec![i32::MIN, i32::MAX]), ); + } + { + let _guard = Guard::new(); assert_eq!( list_minmax64(&[u64::MIN, u64::MAX], &[i64::MIN, i64::MAX]), (vec![u64::MIN, u64::MAX], vec![i64::MIN, i64::MAX]), ); + } + { + let _guard = Guard::new(); assert_eq!( list_minmax_float( &[f32::MIN, f32::MAX, f32::NEG_INFINITY, f32::INFINITY], @@ -72,91 +158,3 @@ impl Guest for Component { ); } } - -impl exports::test::lists::test::Guest for Component { - fn empty_list_param(a: Vec) { - assert!(a.is_empty()); - } - - fn empty_string_param(a: String) { - assert!(a.is_empty()); - } - - fn empty_list_result() -> Vec { - Vec::new() - } - - fn empty_string_result() -> String { - String::new() - } - - fn list_param(list: Vec) { - assert_eq!(list, [1, 2, 3, 4]); - } - - fn list_param2(ptr: String) { - assert_eq!(ptr, "foo"); - } - - fn list_param3(ptr: Vec) { - assert_eq!(ptr.len(), 3); - assert_eq!(ptr[0], "foo"); - assert_eq!(ptr[1], "bar"); - assert_eq!(ptr[2], "baz"); - } - - fn list_param4(ptr: Vec>) { - assert_eq!(ptr.len(), 2); - assert_eq!(ptr[0][0], "foo"); - assert_eq!(ptr[0][1], "bar"); - assert_eq!(ptr[1][0], "baz"); - } - - fn list_param5(ptr: Vec<(u8, u32, u8)>) { - assert_eq!(ptr, [(1, 2, 3), (4, 5, 6)]); - } - - fn list_param_large(ptr: Vec) { - assert_eq!(ptr.len(), 1000); - } - - fn list_result() -> Vec { - vec![1, 2, 3, 4, 5] - } - - fn list_result2() -> String { - "hello!".to_string() - } - - fn list_result3() -> Vec { - vec!["hello,".to_string(), "world!".to_string()] - } - - fn list_roundtrip(x: Vec) -> Vec { - x.clone() - } - - fn string_roundtrip(x: String) -> String { - x.clone() - } - - fn list_minmax8(a: Vec, b: Vec) -> (Vec, Vec) { - (a, b) - } - - fn list_minmax16(a: Vec, b: Vec) -> (Vec, Vec) { - (a, b) - } - - fn list_minmax32(a: Vec, b: Vec) -> (Vec, Vec) { - (a, b) - } - - fn list_minmax64(a: Vec, b: Vec) -> (Vec, Vec) { - (a, b) - } - - fn list_minmax_float(a: Vec, b: Vec) -> (Vec, Vec) { - (a, b) - } -} diff --git a/tests/runtime-new/lists/test.c b/tests/runtime-new/lists/test.c new file mode 100644 index 000000000..e7a91bd97 --- /dev/null +++ b/tests/runtime-new/lists/test.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test.h" + +uint32_t exports_test_lists_to_test_allocated_bytes(void) { + // TODO: should ideally fill this out + return 0; +} + +void exports_test_lists_to_test_empty_list_param(test_list_u8_t *a) { + assert(a->len == 0); +} + +void exports_test_lists_to_test_empty_string_param(test_string_t *a) { + assert(a->len == 0); +} + +void exports_test_lists_to_test_empty_list_result(test_list_u8_t *ret0) { + ret0->ptr = 0; + ret0->len = 0; +} + +void exports_test_lists_to_test_empty_string_result(test_string_t *ret0) { + ret0->ptr = 0; + ret0->len = 0; +} + +void exports_test_lists_to_test_list_param(test_list_u8_t *a) { + assert(a->len == 4); + assert(a->ptr[0] == 1); + assert(a->ptr[1] == 2); + assert(a->ptr[2] == 3); + assert(a->ptr[3] == 4); + test_list_u8_free(a); +} + +void exports_test_lists_to_test_list_param2(test_string_t *a) { + assert(a->len == 3); + assert(a->ptr[0] == 'f'); + assert(a->ptr[1] == 'o'); + assert(a->ptr[2] == 'o'); + test_string_free(a); +} + +void exports_test_lists_to_test_list_param3(test_list_string_t *a) { + assert(a->len == 3); + assert(a->ptr[0].len == 3); + assert(a->ptr[0].ptr[0] == 'f'); + assert(a->ptr[0].ptr[1] == 'o'); + assert(a->ptr[0].ptr[2] == 'o'); + + assert(a->ptr[1].len == 3); + assert(a->ptr[1].ptr[0] == 'b'); + assert(a->ptr[1].ptr[1] == 'a'); + assert(a->ptr[1].ptr[2] == 'r'); + + assert(a->ptr[2].len == 3); + assert(a->ptr[2].ptr[0] == 'b'); + assert(a->ptr[2].ptr[1] == 'a'); + assert(a->ptr[2].ptr[2] == 'z'); + + test_list_string_free(a); +} + +void exports_test_lists_to_test_list_param4(test_list_list_string_t *a) { + assert(a->len == 2); + assert(a->ptr[0].len == 2); + assert(a->ptr[1].len == 1); + + assert(a->ptr[0].ptr[0].len == 3); + assert(a->ptr[0].ptr[0].ptr[0] == 'f'); + assert(a->ptr[0].ptr[0].ptr[1] == 'o'); + assert(a->ptr[0].ptr[0].ptr[2] == 'o'); + + assert(a->ptr[0].ptr[1].len == 3); + assert(a->ptr[0].ptr[1].ptr[0] == 'b'); + assert(a->ptr[0].ptr[1].ptr[1] == 'a'); + assert(a->ptr[0].ptr[1].ptr[2] == 'r'); + + assert(a->ptr[1].ptr[0].len == 3); + assert(a->ptr[1].ptr[0].ptr[0] == 'b'); + assert(a->ptr[1].ptr[0].ptr[1] == 'a'); + assert(a->ptr[1].ptr[0].ptr[2] == 'z'); + + test_list_list_string_free(a); +} + +void exports_test_lists_to_test_list_param_large(test_list_string_t *a) { + assert(a->len == 1000); + test_list_string_free(a); +} + +void exports_test_lists_to_test_list_param5(test_list_tuple3_u8_u32_u8_t *a) { + assert(a->len == 2); + assert(a->ptr[0].f0 == 1); + assert(a->ptr[0].f1 == 2); + assert(a->ptr[0].f2 == 3); + assert(a->ptr[1].f0 == 4); + assert(a->ptr[1].f1 == 5); + assert(a->ptr[1].f2 == 6); + test_list_tuple3_u8_u32_u8_free(a); +} + +void exports_test_lists_to_test_list_result(test_list_u8_t *ret0) { + ret0->ptr = (uint8_t *) malloc(5); + ret0->len = 5; + ret0->ptr[0] = 1; + ret0->ptr[1] = 2; + ret0->ptr[2] = 3; + ret0->ptr[3] = 4; + ret0->ptr[4] = 5; +} + +void exports_test_lists_to_test_list_result2(test_string_t *ret0) { + test_string_dup(ret0, "hello!"); +} + +void exports_test_lists_to_test_list_result3(test_list_string_t *ret0) { + ret0->len = 2; + ret0->ptr = (test_string_t *) malloc(2 * sizeof(test_string_t)); + + test_string_dup(&ret0->ptr[0], "hello,"); + test_string_dup(&ret0->ptr[1], "world!"); +} + +void exports_test_lists_to_test_list_roundtrip(test_list_u8_t *a, test_list_u8_t *ret0) { + *ret0 = *a; +} + +void exports_test_lists_to_test_string_roundtrip(test_string_t *a, test_string_t *ret0) { + *ret0 = *a; +} + +void exports_test_lists_to_test_list_minmax8(test_list_u8_t *a, test_list_s8_t *b, test_tuple2_list_u8_list_s8_t *ret) { + ret->f0 = *a; + ret->f1 = *b; +} + +void exports_test_lists_to_test_list_minmax16(test_list_u16_t *a, test_list_s16_t *b, test_tuple2_list_u16_list_s16_t *ret) { + ret->f0 = *a; + ret->f1 = *b; +} + +void exports_test_lists_to_test_list_minmax32(test_list_u32_t *a, test_list_s32_t *b, test_tuple2_list_u32_list_s32_t *ret) { + ret->f0 = *a; + ret->f1 = *b; +} + +void exports_test_lists_to_test_list_minmax64(test_list_u64_t *a, test_list_s64_t *b, test_tuple2_list_u64_list_s64_t *ret) { + ret->f0 = *a; + ret->f1 = *b; +} + +void exports_test_lists_to_test_list_minmax_float(test_list_f32_t *a, test_list_f64_t *b, test_tuple2_list_f32_list_f64_t *ret) { + ret->f0 = *a; + ret->f1 = *b; +} diff --git a/tests/runtime-new/lists/test.rs b/tests/runtime-new/lists/test.rs new file mode 100644 index 000000000..a0e858c87 --- /dev/null +++ b/tests/runtime-new/lists/test.rs @@ -0,0 +1,99 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +mod alloc; + +impl exports::test::lists::to_test::Guest for Component { + fn allocated_bytes() -> u32 { + alloc::get().try_into().unwrap() + } + + fn empty_list_param(a: Vec) { + assert!(a.is_empty()); + } + + fn empty_string_param(a: String) { + assert!(a.is_empty()); + } + + fn empty_list_result() -> Vec { + Vec::new() + } + + fn empty_string_result() -> String { + String::new() + } + + fn list_param(list: Vec) { + assert_eq!(list, [1, 2, 3, 4]); + } + + fn list_param2(ptr: String) { + assert_eq!(ptr, "foo"); + } + + fn list_param3(ptr: Vec) { + assert_eq!(ptr.len(), 3); + assert_eq!(ptr[0], "foo"); + assert_eq!(ptr[1], "bar"); + assert_eq!(ptr[2], "baz"); + } + + fn list_param4(ptr: Vec>) { + assert_eq!(ptr.len(), 2); + assert_eq!(ptr[0][0], "foo"); + assert_eq!(ptr[0][1], "bar"); + assert_eq!(ptr[1][0], "baz"); + } + + fn list_param5(ptr: Vec<(u8, u32, u8)>) { + assert_eq!(ptr, [(1, 2, 3), (4, 5, 6)]); + } + + fn list_param_large(ptr: Vec) { + assert_eq!(ptr.len(), 1000); + } + + fn list_result() -> Vec { + vec![1, 2, 3, 4, 5] + } + + fn list_result2() -> String { + "hello!".to_string() + } + + fn list_result3() -> Vec { + vec!["hello,".to_string(), "world!".to_string()] + } + + fn list_roundtrip(x: Vec) -> Vec { + x.clone() + } + + fn string_roundtrip(x: String) -> String { + x.clone() + } + + fn list_minmax8(a: Vec, b: Vec) -> (Vec, Vec) { + (a, b) + } + + fn list_minmax16(a: Vec, b: Vec) -> (Vec, Vec) { + (a, b) + } + + fn list_minmax32(a: Vec, b: Vec) -> (Vec, Vec) { + (a, b) + } + + fn list_minmax64(a: Vec, b: Vec) -> (Vec, Vec) { + (a, b) + } + + fn list_minmax_float(a: Vec, b: Vec) -> (Vec, Vec) { + (a, b) + } +} diff --git a/tests/runtime-new/lists/test.wit b/tests/runtime-new/lists/test.wit new file mode 100644 index 000000000..547508e88 --- /dev/null +++ b/tests/runtime-new/lists/test.wit @@ -0,0 +1,39 @@ +package test:lists; + +interface to-test { + empty-list-param: func(a: list); + empty-string-param: func(a: string); + empty-list-result: func() -> list; + empty-string-result: func() -> string; + + list-param: func(a: list); + list-param2: func(a: string); + list-param3: func(a: list); + list-param4: func(a: list>); + list-param5: func(a: list>); + list-param-large: func(a: list); + list-result: func() -> list; + list-result2: func() -> string; + list-result3: func() -> list; + + list-minmax8: func(a: list, b: list) -> tuple, list>; + list-minmax16: func(a: list, b: list) -> tuple, list>; + list-minmax32: func(a: list, b: list) -> tuple, list>; + list-minmax64: func(a: list, b: list) -> tuple, list>; + list-minmax-float: func(a: list, b: list) + -> tuple, list>; + + list-roundtrip: func(a: list) -> list; + + string-roundtrip: func(a: string) -> string; + + allocated-bytes: func() -> u32; +} + +world test { + export to-test; +} + +world runner { + import to-test; +} diff --git a/tests/runtime-new/numbers/runner.c b/tests/runtime-new/numbers/runner.c new file mode 100644 index 000000000..ccc348760 --- /dev/null +++ b/tests/runtime-new/numbers/runner.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +int main() { + assert(test_numbers_numbers_roundtrip_u8(1) == 1); + assert(test_numbers_numbers_roundtrip_u8(0) == 0); + assert(test_numbers_numbers_roundtrip_u8(UCHAR_MAX) == UCHAR_MAX); + + assert(test_numbers_numbers_roundtrip_s8(1) == 1); + assert(test_numbers_numbers_roundtrip_s8(SCHAR_MIN) == SCHAR_MIN); + assert(test_numbers_numbers_roundtrip_s8(SCHAR_MAX) == SCHAR_MAX); + + assert(test_numbers_numbers_roundtrip_u16(1) == 1); + assert(test_numbers_numbers_roundtrip_u16(0) == 0); + assert(test_numbers_numbers_roundtrip_u16(USHRT_MAX) == USHRT_MAX); + + assert(test_numbers_numbers_roundtrip_s16(1) == 1); + assert(test_numbers_numbers_roundtrip_s16(SHRT_MIN) == SHRT_MIN); + assert(test_numbers_numbers_roundtrip_s16(SHRT_MAX) == SHRT_MAX); + + assert(test_numbers_numbers_roundtrip_u32(1) == 1); + assert(test_numbers_numbers_roundtrip_u32(0) == 0); + assert(test_numbers_numbers_roundtrip_u32(UINT_MAX) == UINT_MAX); + + assert(test_numbers_numbers_roundtrip_s32(1) == 1); + assert(test_numbers_numbers_roundtrip_s32(INT_MIN) == INT_MIN); + assert(test_numbers_numbers_roundtrip_s32(INT_MAX) == INT_MAX); + + assert(test_numbers_numbers_roundtrip_u64(1) == 1); + assert(test_numbers_numbers_roundtrip_u64(0) == 0); + assert(test_numbers_numbers_roundtrip_u64(ULONG_MAX) == ULONG_MAX); + + assert(test_numbers_numbers_roundtrip_s64(1) == 1); + assert(test_numbers_numbers_roundtrip_s64(LONG_MIN) == LONG_MIN); + assert(test_numbers_numbers_roundtrip_s64(LONG_MAX) == LONG_MAX); + + assert(test_numbers_numbers_roundtrip_f32(1.0) == 1.0); + assert(test_numbers_numbers_roundtrip_f32(INFINITY) == INFINITY); + assert(test_numbers_numbers_roundtrip_f32(-INFINITY) == -INFINITY); + assert(isnan(test_numbers_numbers_roundtrip_f32(NAN))); + + assert(test_numbers_numbers_roundtrip_f64(1.0) == 1.0); + assert(test_numbers_numbers_roundtrip_f64(INFINITY) == INFINITY); + assert(test_numbers_numbers_roundtrip_f64(-INFINITY) == -INFINITY); + assert(isnan(test_numbers_numbers_roundtrip_f64(NAN))); + + assert(test_numbers_numbers_roundtrip_char('a') == 'a'); + assert(test_numbers_numbers_roundtrip_char(' ') == ' '); + assert(test_numbers_numbers_roundtrip_char(U'🚩') == U'🚩'); + + test_numbers_numbers_set_scalar(2); + assert(test_numbers_numbers_get_scalar() == 2); + test_numbers_numbers_set_scalar(4); + assert(test_numbers_numbers_get_scalar() == 4); + + return 0; +} diff --git a/tests/runtime-new/numbers/runner.rs b/tests/runtime-new/numbers/runner.rs new file mode 100644 index 000000000..8e4e08416 --- /dev/null +++ b/tests/runtime-new/numbers/runner.rs @@ -0,0 +1,55 @@ +include!(env!("BINDINGS")); + +fn main() { + use test::numbers::numbers::*; + assert_eq!(roundtrip_u8(1), 1); + assert_eq!(roundtrip_u8(u8::min_value()), u8::min_value()); + assert_eq!(roundtrip_u8(u8::max_value()), u8::max_value()); + + assert_eq!(roundtrip_s8(1), 1); + assert_eq!(roundtrip_s8(i8::min_value()), i8::min_value()); + assert_eq!(roundtrip_s8(i8::max_value()), i8::max_value()); + + assert_eq!(roundtrip_u16(1), 1); + assert_eq!(roundtrip_u16(u16::min_value()), u16::min_value()); + assert_eq!(roundtrip_u16(u16::max_value()), u16::max_value()); + + assert_eq!(roundtrip_s16(1), 1); + assert_eq!(roundtrip_s16(i16::min_value()), i16::min_value()); + assert_eq!(roundtrip_s16(i16::max_value()), i16::max_value()); + + assert_eq!(roundtrip_u32(1), 1); + assert_eq!(roundtrip_u32(u32::min_value()), u32::min_value()); + assert_eq!(roundtrip_u32(u32::max_value()), u32::max_value()); + + assert_eq!(roundtrip_s32(1), 1); + assert_eq!(roundtrip_s32(i32::min_value()), i32::min_value()); + assert_eq!(roundtrip_s32(i32::max_value()), i32::max_value()); + + assert_eq!(roundtrip_u64(1), 1); + assert_eq!(roundtrip_u64(u64::min_value()), u64::min_value()); + assert_eq!(roundtrip_u64(u64::max_value()), u64::max_value()); + + assert_eq!(roundtrip_s64(1), 1); + assert_eq!(roundtrip_s64(i64::min_value()), i64::min_value()); + assert_eq!(roundtrip_s64(i64::max_value()), i64::max_value()); + + assert_eq!(roundtrip_f32(1.0), 1.0); + assert_eq!(roundtrip_f32(f32::INFINITY), f32::INFINITY); + assert_eq!(roundtrip_f32(f32::NEG_INFINITY), f32::NEG_INFINITY); + assert!(roundtrip_f32(f32::NAN).is_nan()); + + assert_eq!(roundtrip_f64(1.0), 1.0); + assert_eq!(roundtrip_f64(f64::INFINITY), f64::INFINITY); + assert_eq!(roundtrip_f64(f64::NEG_INFINITY), f64::NEG_INFINITY); + assert!(roundtrip_f64(f64::NAN).is_nan()); + + assert_eq!(roundtrip_char('a'), 'a'); + assert_eq!(roundtrip_char(' '), ' '); + assert_eq!(roundtrip_char('🚩'), '🚩'); + + set_scalar(2); + assert_eq!(get_scalar(), 2); + set_scalar(4); + assert_eq!(get_scalar(), 4); +} diff --git a/tests/runtime-new/numbers/test.c b/tests/runtime-new/numbers/test.c new file mode 100644 index 000000000..b5ca643f4 --- /dev/null +++ b/tests/runtime-new/numbers/test.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +uint8_t exports_test_numbers_numbers_roundtrip_u8(uint8_t a) { + return a; +} + +int8_t exports_test_numbers_numbers_roundtrip_s8(int8_t a) { + return a; +} + +uint16_t exports_test_numbers_numbers_roundtrip_u16(uint16_t a) { + return a; +} + +int16_t exports_test_numbers_numbers_roundtrip_s16(int16_t a) { + return a; +} + +uint32_t exports_test_numbers_numbers_roundtrip_u32(uint32_t a) { + return a; +} + +int32_t exports_test_numbers_numbers_roundtrip_s32(int32_t a) { + return a; +} + +uint64_t exports_test_numbers_numbers_roundtrip_u64(uint64_t a) { + return a; +} + +int64_t exports_test_numbers_numbers_roundtrip_s64(int64_t a) { + return a; +} + +float exports_test_numbers_numbers_roundtrip_f32(float a) { + return a; +} + +double exports_test_numbers_numbers_roundtrip_f64(double a) { + return a; +} + +uint32_t exports_test_numbers_numbers_roundtrip_char(uint32_t a) { + return a; +} + +static uint32_t SCALAR = 0; + +void exports_test_numbers_numbers_set_scalar(uint32_t a) { + SCALAR = a; +} + +uint32_t exports_test_numbers_numbers_get_scalar(void) { + return SCALAR; +} diff --git a/tests/runtime-new/numbers/test.rs b/tests/runtime-new/numbers/test.rs new file mode 100644 index 000000000..0b7997e23 --- /dev/null +++ b/tests/runtime-new/numbers/test.rs @@ -0,0 +1,63 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use std::sync::atomic::{AtomicU32, Ordering::SeqCst}; + +static SCALAR: AtomicU32 = AtomicU32::new(0); + +impl exports::test::numbers::numbers::Guest for Component { + fn roundtrip_u8(a: u8) -> u8 { + a + } + + fn roundtrip_s8(a: i8) -> i8 { + a + } + + fn roundtrip_u16(a: u16) -> u16 { + a + } + + fn roundtrip_s16(a: i16) -> i16 { + a + } + + fn roundtrip_u32(a: u32) -> u32 { + a + } + + fn roundtrip_s32(a: i32) -> i32 { + a + } + + fn roundtrip_u64(a: u64) -> u64 { + a + } + + fn roundtrip_s64(a: i64) -> i64 { + a + } + + fn roundtrip_f32(a: f32) -> f32 { + a + } + + fn roundtrip_f64(a: f64) -> f64 { + a + } + + fn roundtrip_char(a: char) -> char { + a + } + + fn set_scalar(val: u32) { + SCALAR.store(val, SeqCst) + } + + fn get_scalar() -> u32 { + SCALAR.load(SeqCst) + } +} diff --git a/tests/runtime-new/numbers/test.wit b/tests/runtime-new/numbers/test.wit new file mode 100644 index 000000000..7019bc3cd --- /dev/null +++ b/tests/runtime-new/numbers/test.wit @@ -0,0 +1,26 @@ +package test:numbers; + +interface numbers { + roundtrip-u8: func(a: u8) -> u8; + roundtrip-s8: func(a: s8) -> s8; + roundtrip-u16: func(a: u16) -> u16; + roundtrip-s16: func(a: s16) -> s16; + roundtrip-u32: func(a: u32) -> u32; + roundtrip-s32: func(a: s32) -> s32; + roundtrip-u64: func(a: u64) -> u64; + roundtrip-s64: func(a: s64) -> s64; + roundtrip-f32: func(a: f32) -> f32; + roundtrip-f64: func(a: f64) -> f64; + roundtrip-char: func(a: char) -> char; + + set-scalar: func(a: u32); + get-scalar: func() -> u32; +} + +world test { + export numbers; +} + +world runner { + import numbers; +} diff --git a/tests/runtime-new/package-with-version/runner.rs b/tests/runtime-new/package-with-version/runner.rs new file mode 100644 index 000000000..36cfec7af --- /dev/null +++ b/tests/runtime-new/package-with-version/runner.rs @@ -0,0 +1,7 @@ +include!(env!("BINDINGS")); + +use crate::my::inline::foo::Bar; + +fn main() { + let _ = Bar::new(); +} diff --git a/tests/runtime-new/package-with-version/test.rs b/tests/runtime-new/package-with-version/test.rs new file mode 100644 index 000000000..4059ecebb --- /dev/null +++ b/tests/runtime-new/package-with-version/test.rs @@ -0,0 +1,17 @@ +include!(env!("BINDINGS")); + +pub struct MyResource; + +impl exports::my::inline::foo::GuestBar for MyResource { + fn new() -> Self { + MyResource + } +} + +struct Component; + +impl exports::my::inline::foo::Guest for Component { + type Bar = MyResource; +} + +export!(Component); diff --git a/tests/runtime-new/package-with-version/test.wit b/tests/runtime-new/package-with-version/test.wit new file mode 100644 index 000000000..01367228a --- /dev/null +++ b/tests/runtime-new/package-with-version/test.wit @@ -0,0 +1,14 @@ +package my:inline@0.0.0; + +interface foo { + resource bar { + constructor(); + } +} + +world test { + export foo; +} +world runner { + import foo; +} diff --git a/tests/runtime-new/rust/alternative-bitflags/runner.rs b/tests/runtime-new/rust/alternative-bitflags/runner.rs new file mode 100644 index 000000000..ffca51d80 --- /dev/null +++ b/tests/runtime-new/rust/alternative-bitflags/runner.rs @@ -0,0 +1,11 @@ +//@ args = '--bitflags-path crate::my_bitflags' + +include!(env!("BINDINGS")); + +pub(crate) use wit_bindgen::bitflags as my_bitflags; + +use crate::my::inline::t::{get_flag, Bar}; + +fn main() { + assert_eq!(get_flag(), Bar::BAZ); +} diff --git a/tests/runtime-new/rust/alternative-bitflags/test.rs b/tests/runtime-new/rust/alternative-bitflags/test.rs new file mode 100644 index 000000000..3cb2098df --- /dev/null +++ b/tests/runtime-new/rust/alternative-bitflags/test.rs @@ -0,0 +1,17 @@ +//@ args = '--bitflags-path crate::my_bitflags' + +include!(env!("BINDINGS")); + +pub(crate) use wit_bindgen::bitflags as my_bitflags; + +struct Component; + +export!(Component); + +use crate::exports::my::inline::t::{Bar, Guest}; + +impl Guest for Component { + fn get_flag() -> Bar { + Bar::BAZ + } +} diff --git a/tests/runtime-new/rust/alternative-bitflags/test.wit b/tests/runtime-new/rust/alternative-bitflags/test.wit new file mode 100644 index 000000000..b7682b4be --- /dev/null +++ b/tests/runtime-new/rust/alternative-bitflags/test.wit @@ -0,0 +1,19 @@ +package my:inline; + +interface t { + flags bar { + foo, + bar, + baz + } + + get-flag: func() -> bar; +} + +world test { + export t; +} + +world runner { + import t; +} diff --git a/tests/runtime-new/rust/custom-derives/runner.rs b/tests/runtime-new/rust/custom-derives/runner.rs new file mode 100644 index 000000000..fa5fe4564 --- /dev/null +++ b/tests/runtime-new/rust/custom-derives/runner.rs @@ -0,0 +1,10 @@ +include!(env!("BINDINGS")); + +use crate::my::inline::blah::{bar, Foo}; + +fn main() { + bar(&Foo { + field1: "x".to_string(), + field2: vec![2, 3, 3, 4], + }); +} diff --git a/tests/runtime-new/rust/custom-derives/test.rs b/tests/runtime-new/rust/custom-derives/test.rs new file mode 100644 index 000000000..e301c25d0 --- /dev/null +++ b/tests/runtime-new/rust/custom-derives/test.rs @@ -0,0 +1,43 @@ +//@ args = [ +//@ '-dHash', +//@ '-dClone', +//@ '-dstd::cmp::PartialEq', +//@ '-dcore::cmp::Eq', +//@ '--additional-derive-ignore=ignoreme', +//@ ] + +include!(env!("BINDINGS")); + +use crate::exports::my::inline::blag; +use crate::exports::my::inline::blah::{Foo, Guest, Ignoreme}; +use std::collections::{hash_map::RandomState, HashSet}; + +struct Component; + +impl Guest for Component { + fn bar(cool: Foo) { + let _blah: HashSet = HashSet::from_iter([ + Foo { + field1: "hello".to_string(), + field2: vec![1, 2, 3], + }, + cool, + ]); + } + + fn barry(_: Ignoreme) {} +} + +struct MyInputStream; + +impl blag::Guest for Component { + type InputStream = MyInputStream; +} + +impl blag::GuestInputStream for MyInputStream { + fn read(&self, _len: u64) -> Vec { + todo!() + } +} + +export!(Component); diff --git a/tests/runtime-new/rust/custom-derives/test.wit b/tests/runtime-new/rust/custom-derives/test.wit new file mode 100644 index 000000000..7a81e8106 --- /dev/null +++ b/tests/runtime-new/rust/custom-derives/test.wit @@ -0,0 +1,31 @@ +package my:inline; + +interface blag { + resource input-stream { + read: func(len: u64) -> list; + } +} + +interface blah { + use blag.{input-stream}; + record foo { + field1: string, + field2: list + } + + bar: func(cool: foo); + + variant ignoreme { + stream-type(input-stream), + } + + barry: func(warm: ignoreme); +} + +world test { + export blag; + export blah; +} +world runner { + import blah; +} diff --git a/tests/runtime-new/rust/disable-custom-section-link-helpers/runner.rs b/tests/runtime-new/rust/disable-custom-section-link-helpers/runner.rs new file mode 100644 index 000000000..f181a41a2 --- /dev/null +++ b/tests/runtime-new/rust/disable-custom-section-link-helpers/runner.rs @@ -0,0 +1,9 @@ +//@ args = '--disable-custom-section-link-helpers' + +include!(env!("BINDINGS")); + +use crate::a::x; + +fn main() { + x(); +} diff --git a/tests/runtime-new/rust/disable-custom-section-link-helpers/test.rs b/tests/runtime-new/rust/disable-custom-section-link-helpers/test.rs new file mode 100644 index 000000000..01d6a3e33 --- /dev/null +++ b/tests/runtime-new/rust/disable-custom-section-link-helpers/test.rs @@ -0,0 +1,11 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::a::Guest; + +impl Guest for Component { + fn x() {} +} diff --git a/tests/runtime-new/rust/disable-custom-section-link-helpers/test.wit b/tests/runtime-new/rust/disable-custom-section-link-helpers/test.wit new file mode 100644 index 000000000..5ed76033a --- /dev/null +++ b/tests/runtime-new/rust/disable-custom-section-link-helpers/test.wit @@ -0,0 +1,12 @@ +package a:b; + +world runner { + import a: interface { + x: func(); + } +} +world test { + export a: interface { + x: func(); + } +} diff --git a/tests/runtime-new/rust/owned-resource-deref-mut/runner.rs b/tests/runtime-new/rust/owned-resource-deref-mut/runner.rs new file mode 100644 index 000000000..0b078e6eb --- /dev/null +++ b/tests/runtime-new/rust/owned-resource-deref-mut/runner.rs @@ -0,0 +1,9 @@ +include!(env!("BINDINGS")); + +use crate::my::inline::foo::Bar; + +fn main() { + let data = Bar::new(3); + assert_eq!(data.get_data(), 3); + assert_eq!(Bar::consume(data), 4); +} diff --git a/tests/runtime-new/rust/owned-resource-deref-mut/test.rs b/tests/runtime-new/rust/owned-resource-deref-mut/test.rs new file mode 100644 index 000000000..8005f333e --- /dev/null +++ b/tests/runtime-new/rust/owned-resource-deref-mut/test.rs @@ -0,0 +1,33 @@ +include!(env!("BINDINGS")); + +pub struct MyResource { + data: u32, +} + +impl exports::my::inline::foo::GuestBar for MyResource { + fn new(data: u32) -> Self { + Self { data } + } + + fn get_data(&self) -> u32 { + self.data + } + + fn consume(mut this: exports::my::inline::foo::Bar) -> u32 { + let me: &MyResource = this.get(); + let prior_data: &u32 = &me.data; + let new_data = prior_data + 1; + let me: &mut MyResource = this.get_mut(); + let mutable_data: &mut u32 = &mut me.data; + *mutable_data = new_data; + me.data + } +} + +struct Component; + +impl exports::my::inline::foo::Guest for Component { + type Bar = MyResource; +} + +export!(Component); diff --git a/tests/runtime-new/rust/owned-resource-deref-mut/test.wit b/tests/runtime-new/rust/owned-resource-deref-mut/test.wit new file mode 100644 index 000000000..beee3826b --- /dev/null +++ b/tests/runtime-new/rust/owned-resource-deref-mut/test.wit @@ -0,0 +1,16 @@ +package my:inline; + +interface foo { + resource bar { + constructor(data: u32); + get-data: func() -> u32; + consume: static func(%self: bar) -> u32; + } +} + +world test { + export foo; +} +world runner { + import foo; +} diff --git a/tests/runtime-new/rust/raw-strings/runner-nostd.rs b/tests/runtime-new/rust/raw-strings/runner-nostd.rs new file mode 100644 index 000000000..d6dfd1b27 --- /dev/null +++ b/tests/runtime-new/rust/raw-strings/runner-nostd.rs @@ -0,0 +1,18 @@ +//@ args = '--std-feature --raw-strings' + +#![no_std] + +extern crate alloc; + +use alloc::vec::Vec; + +include!(env!("BINDINGS")); + +fn main() { + // Test the argument is `&str` + cat::foo(b"hello"); + + // Test the return type is `String` + let t: Vec = cat::bar(); + assert_eq!(t, b"world"); +} diff --git a/tests/runtime-new/rust/raw-strings/runner-std.rs b/tests/runtime-new/rust/raw-strings/runner-std.rs new file mode 100644 index 000000000..993e1848d --- /dev/null +++ b/tests/runtime-new/rust/raw-strings/runner-std.rs @@ -0,0 +1,12 @@ +//@ args = '--raw-strings' + +include!(env!("BINDINGS")); + +fn main() { + // Test the argument is `&[u8]` + cat::foo(b"hello"); + + // Test the return type is `Vec` + let t: Vec = cat::bar(); + assert_eq!(t, b"world"); +} diff --git a/tests/runtime-new/rust/raw-strings/test.rs b/tests/runtime-new/rust/raw-strings/test.rs new file mode 100644 index 000000000..eca51ccc0 --- /dev/null +++ b/tests/runtime-new/rust/raw-strings/test.rs @@ -0,0 +1,17 @@ +// TODO: this test fails to compile with `--raw-strings`, that should be fixed. + +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +impl exports::cat::Guest for Test { + fn foo(x: String) { + assert_eq!(x, "hello"); + } + + fn bar() -> String { + "world".into() + } +} diff --git a/tests/runtime-new/rust/raw-strings/test.wit b/tests/runtime-new/rust/raw-strings/test.wit new file mode 100644 index 000000000..96e70a1a1 --- /dev/null +++ b/tests/runtime-new/rust/raw-strings/test.wit @@ -0,0 +1,15 @@ +package my:strings; + +world runner { + import cat: interface { + foo: func(x: string); + bar: func() -> string; + } +} + +world test { + export cat: interface { + foo: func(x: string); + bar: func() -> string; + } +} diff --git a/tests/runtime-new/rust/run-ctors-once-workaround/runner.rs b/tests/runtime-new/rust/run-ctors-once-workaround/runner.rs new file mode 100644 index 000000000..010cbffb8 --- /dev/null +++ b/tests/runtime-new/rust/run-ctors-once-workaround/runner.rs @@ -0,0 +1,7 @@ +//@ args = ['--disable-run-ctors-once-workaround'] + +include!(env!("BINDINGS")); + +fn main() { + the::test::i::apply_the_workaround(); +} diff --git a/tests/runtime-new/rust/run-ctors-once-workaround/test.rs b/tests/runtime-new/rust/run-ctors-once-workaround/test.rs new file mode 100644 index 000000000..f5590ff4f --- /dev/null +++ b/tests/runtime-new/rust/run-ctors-once-workaround/test.rs @@ -0,0 +1,11 @@ +//@ args = ['--disable-run-ctors-once-workaround'] + +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +impl exports::the::test::i::Guest for Test { + fn apply_the_workaround() {} +} diff --git a/tests/runtime-new/rust/run-ctors-once-workaround/test.wit b/tests/runtime-new/rust/run-ctors-once-workaround/test.wit new file mode 100644 index 000000000..c14aed482 --- /dev/null +++ b/tests/runtime-new/rust/run-ctors-once-workaround/test.wit @@ -0,0 +1,13 @@ +package the:test; + +interface i { + apply-the-workaround: func(); +} + +world runner { + import i; +} + +world test { + export i; +} diff --git a/tests/runtime-new/rust/skip/runner.rs b/tests/runtime-new/rust/skip/runner.rs new file mode 100644 index 000000000..8492687af --- /dev/null +++ b/tests/runtime-new/rust/skip/runner.rs @@ -0,0 +1,6 @@ +include!(env!("BINDINGS")); + +fn main() { + exports::foo(); + exports::bar(); +} diff --git a/tests/runtime-new/rust/skip/test-nostd.rs b/tests/runtime-new/rust/skip/test-nostd.rs new file mode 100644 index 000000000..3d9872309 --- /dev/null +++ b/tests/runtime-new/rust/skip/test-nostd.rs @@ -0,0 +1,16 @@ +//@ args = '--skip foo --std-feature' + +#![no_std] + +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +impl exports::exports::Guest for Test { + fn bar() {} +} + +#[unsafe(export_name = "exports#foo")] +pub extern "C" fn foo() {} diff --git a/tests/runtime-new/rust/skip/test-std.rs b/tests/runtime-new/rust/skip/test-std.rs new file mode 100644 index 000000000..c04e49177 --- /dev/null +++ b/tests/runtime-new/rust/skip/test-std.rs @@ -0,0 +1,14 @@ +//@ args = '--skip foo' + +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +impl exports::exports::Guest for Test { + fn bar() {} +} + +#[unsafe(export_name = "exports#foo")] +pub extern "C" fn foo() {} diff --git a/tests/runtime-new/rust/skip/test.wit b/tests/runtime-new/rust/skip/test.wit new file mode 100644 index 000000000..768b02858 --- /dev/null +++ b/tests/runtime-new/rust/skip/test.wit @@ -0,0 +1,15 @@ +package my:test; + +world runner { + import exports: interface { + foo: func(); + bar: func(); + } +} + +world test { + export exports: interface { + foo: func(); + bar: func(); + } +} diff --git a/tests/runtime-new/rust/with-and-resources/runner.rs b/tests/runtime-new/rust/with-and-resources/runner.rs new file mode 100644 index 000000000..c1c3c423d --- /dev/null +++ b/tests/runtime-new/rust/with-and-resources/runner.rs @@ -0,0 +1,26 @@ +//@ args = '--with my:inline/foo=other::my::inline::foo' + +include!(env!("BINDINGS")); + +mod other { + wit_bindgen::generate!({ + inline: " + package my:inline; + + interface foo { + resource a; + + bar: func() -> a; + } + + world dummy { + import foo; + } + ", + }); +} + +fn main() { + let resource = other::my::inline::foo::bar(); + my::inline::bar::bar(resource); +} diff --git a/tests/runtime-new/rust/with-and-resources/test.rs b/tests/runtime-new/rust/with-and-resources/test.rs new file mode 100644 index 000000000..7dea11cba --- /dev/null +++ b/tests/runtime-new/rust/with-and-resources/test.rs @@ -0,0 +1,26 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::my::inline::bar::Guest as GuestBar; +use crate::exports::my::inline::foo::{Guest as GuestFoo, GuestA, A}; + +struct MyA; + +impl GuestFoo for Component { + type A = MyA; + + fn bar() -> A { + A::new(MyA) + } +} + +impl GuestA for MyA {} + +impl GuestBar for Component { + fn bar(m: A) -> Vec { + vec![m] + } +} diff --git a/tests/runtime-new/rust/with-and-resources/test.wit b/tests/runtime-new/rust/with-and-resources/test.wit new file mode 100644 index 000000000..36c14f53d --- /dev/null +++ b/tests/runtime-new/rust/with-and-resources/test.wit @@ -0,0 +1,22 @@ +package my:inline; + +interface foo { + resource a; + + bar: func() -> a; +} + +interface bar { + use foo.{a}; + + bar: func(m: a) -> list; +} + +world test { + export foo; + export bar; +} + +world runner { + import bar; +} diff --git a/tests/runtime-new/rust/with-option-generate/runner-generate-all.rs b/tests/runtime-new/rust/with-option-generate/runner-generate-all.rs new file mode 100644 index 000000000..c98076c0e --- /dev/null +++ b/tests/runtime-new/rust/with-option-generate/runner-generate-all.rs @@ -0,0 +1,9 @@ +//@ args = '--generate-all' + +include!(env!("BINDINGS")); + +use crate::foo::baz::a::x; + +fn main() { + x(); +} diff --git a/tests/runtime-new/rust/with-option-generate/runner-generate-one.rs b/tests/runtime-new/rust/with-option-generate/runner-generate-one.rs new file mode 100644 index 000000000..24adc396e --- /dev/null +++ b/tests/runtime-new/rust/with-option-generate/runner-generate-one.rs @@ -0,0 +1,9 @@ +//@ args = '--with foo:baz/a=generate' + +include!(env!("BINDINGS")); + +use crate::foo::baz::a::x; + +fn main() { + x(); +} diff --git a/tests/runtime-new/rust/with-option-generate/test.rs b/tests/runtime-new/rust/with-option-generate/test.rs new file mode 100644 index 000000000..e64136b49 --- /dev/null +++ b/tests/runtime-new/rust/with-option-generate/test.rs @@ -0,0 +1,13 @@ +//@ args = '--generate-all' + +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +use crate::exports::foo::baz::a::Guest; + +impl Guest for Test { + fn x() {} +} diff --git a/tests/runtime-new/rust/with-option-generate/test.wit b/tests/runtime-new/rust/with-option-generate/test.wit new file mode 100644 index 000000000..ff5cb3f09 --- /dev/null +++ b/tests/runtime-new/rust/with-option-generate/test.wit @@ -0,0 +1,19 @@ +//@ default-bindgen-args = false + +// Note that default bindgen args, where Rust uses `--generate-all`, is +// specifically disabled for this test as that's what's being tested here. + +package foo:bar; + +world test { + export foo:baz/a; +} +world runner { + import foo:baz/a; +} + +package foo:baz { + interface a { + x: func(); + } +} diff --git a/tests/runtime-new/rust/with-types/runner.rs b/tests/runtime-new/rust/with-types/runner.rs new file mode 100644 index 000000000..bdeb749b5 --- /dev/null +++ b/tests/runtime-new/rust/with-types/runner.rs @@ -0,0 +1,68 @@ +//@ args = [ +//@ '--with=my:inline/foo/a=crate::my_types::MyA', +//@ '--with=my:inline/foo/b=crate::my_types::MyB', +//@ '--with=my:inline/foo/c=crate::my_types::MyC', +//@ '--with=d=crate::my_types::MyD', +//@ '--with=my:inline/bar/e=crate::my_types::MyE', +//@ '--with=my:inline/foo/f=generate', +//@ ] + +include!(env!("BINDINGS")); + +mod my_types { + #[derive(Debug, Clone, Copy)] + pub struct MyA { + pub inner: f64, + } + + #[derive(Debug, Clone, Copy)] + pub struct MyB; + + impl MyB { + pub fn take_handle(&self) -> u32 { + 0 + } + + pub fn from_handle(_handle: u32) -> Self { + Self + } + } + + pub enum MyC { + A(MyA), + B(MyB), + } + + pub struct MyD { + pub inner: u32, + } + + pub struct MyE { + pub inner: u32, + } +} + +fn main() { + let a = my_types::MyA { inner: 0.0 }; + let _ = my::inline::foo::func1(a); + + // can't actually succeed at runtime as this is faking a resource, so check + // that it compiles but dynamically skip it. + if false { + let b = my_types::MyB; + let _ = my::inline::foo::func2(b); + } + + let c = my_types::MyC::A(a); + let _ = i::func7(c); + + let a_list = vec![a, a]; + let _ = my::inline::foo::func3(&a_list); + + let _ = my::inline::foo::func4(Some(a)); + + let _ = my::inline::foo::func5(); + + let d = my_types::MyD { inner: 0 }; + let _ = i::func8(d); +} diff --git a/tests/runtime-new/rust/with-types/test.rs b/tests/runtime-new/rust/with-types/test.rs new file mode 100644 index 000000000..c1ed67815 --- /dev/null +++ b/tests/runtime-new/rust/with-types/test.rs @@ -0,0 +1,55 @@ +//@ args = '--generate-all' + +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +use crate::exports::i::{C, D}; +use crate::exports::my::inline::bar::E; +use crate::exports::my::inline::foo::{A, B, F, G}; + +impl exports::my::inline::foo::Guest for Test { + type B = B; + + fn func1(v: A) -> A { + v + } + fn func2(v: B) -> B { + v + } + fn func3(_: Vec) -> Vec { + Vec::new() + } + fn func4(v: Option) -> Option { + v + } + fn func5() -> Result { + Err(()) + } + fn func6() -> Result { + Err(()) + } + fn func7() -> Result { + Err(()) + } +} + +impl exports::my::inline::foo::GuestB for B {} + +impl exports::my::inline::bar::Guest for Test { + fn func6(v: E) -> E { + v + } +} + +impl exports::i::Guest for Test { + fn func7(a: C) -> C { + a + } + + fn func8(a: D) -> D { + a + } +} diff --git a/tests/runtime-new/rust/with-types/test.wit b/tests/runtime-new/rust/with-types/test.wit new file mode 100644 index 000000000..5668dff6e --- /dev/null +++ b/tests/runtime-new/rust/with-types/test.wit @@ -0,0 +1,76 @@ +//@ default-bindgen-args = false + +package my:inline; + +interface foo { + + record a { + inner: f64, + } + + resource b; + + variant c { + a(a), + b(b), + } + + // test type definition generation with `generate` option + record f { + inner: u32, + } + + // test type definition generation without `generate` option + record g { + inner: u32, + } + + func1: func(v: a) -> a; + func2: func(v: b) -> b; + func3: func(v: list) -> list; + func4: func(v: option) -> option; + func5: func() -> result; + func6: func() -> result; + func7: func() -> result; +} + +interface bar { + record e { + inner: u32, + } + + func6: func(v: e) -> e; +} + +world test { + export i: interface { + // test remapping with importing type directly + use foo.{c}; + func7: func(v: c) -> c; + + // test remapping the type defined in world + record d { + inner: u32, + } + + func8: func(v: d) -> d; + } + + export foo; + export bar; +} + +world runner { + import i: interface { + use foo.{c}; + func7: func(v: c) -> c; + + record d { + inner: u32, + } + + func8: func(v: d) -> d; + } + + import bar; +} diff --git a/tests/runtime-new/rust/with/runner.rs b/tests/runtime-new/rust/with/runner.rs new file mode 100644 index 000000000..e16308b0b --- /dev/null +++ b/tests/runtime-new/rust/with/runner.rs @@ -0,0 +1,29 @@ +//@ args = '--with my:inline/foo=other::my::inline::foo' + +include!(env!("BINDINGS")); + +mod other { + wit_bindgen::generate!({ + inline: " + package my:inline; + + interface foo { + record msg { + field: string, + } + } + + world dummy { + use foo.{msg}; + import bar: func(m: msg); + } + ", + }); +} + +fn main() { + let msg = other::my::inline::foo::Msg { + field: "hello".to_string(), + }; + my::inline::bar::bar(&msg); +} diff --git a/tests/runtime-new/rust/with/test.rs b/tests/runtime-new/rust/with/test.rs new file mode 100644 index 000000000..bd3cb90d5 --- /dev/null +++ b/tests/runtime-new/rust/with/test.rs @@ -0,0 +1,13 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::my::inline::bar::{Guest, Msg}; + +impl Guest for Component { + fn bar(m: Msg) { + assert_eq!(m.field, "hello"); + } +} diff --git a/tests/runtime-new/rust/with/test.wit b/tests/runtime-new/rust/with/test.wit new file mode 100644 index 000000000..000c0b2b7 --- /dev/null +++ b/tests/runtime-new/rust/with/test.wit @@ -0,0 +1,21 @@ +package my:inline; + +interface foo { + record msg { + field: string, + } +} + +interface bar { + use foo.{msg}; + + bar: func(m: msg); +} + +world test { + export bar; +} + +world runner { + import bar; +} diff --git a/tests/runtime-new/strings-alias/runner.rs b/tests/runtime-new/strings-alias/runner.rs new file mode 100644 index 000000000..f2c664c2f --- /dev/null +++ b/tests/runtime-new/strings-alias/runner.rs @@ -0,0 +1,10 @@ +include!(env!("BINDINGS")); + +fn main() { + // Test the argument is `&str` + cat::foo("hello"); + + // Test the return type is `String` + let t: String = cat::bar(); + assert_eq!(t, "world"); +} diff --git a/tests/runtime-new/strings-alias/test.rs b/tests/runtime-new/strings-alias/test.rs new file mode 100644 index 000000000..dca7e8fab --- /dev/null +++ b/tests/runtime-new/strings-alias/test.rs @@ -0,0 +1,15 @@ +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +impl exports::cat::Guest for Test { + fn foo(x: String) { + assert_eq!(x, "hello"); + } + + fn bar() -> String { + "world".into() + } +} diff --git a/tests/runtime-new/strings-alias/test.wit b/tests/runtime-new/strings-alias/test.wit new file mode 100644 index 000000000..489a61541 --- /dev/null +++ b/tests/runtime-new/strings-alias/test.wit @@ -0,0 +1,17 @@ +package my:strings; + +world runner { + import cat: interface { + type my-string = string; + foo: func(x: my-string); + bar: func() -> my-string; + } +} + +world test { + export cat: interface { + type my-string = string; + foo: func(x: my-string); + bar: func() -> my-string; + } +} diff --git a/tests/runtime-new/strings-simple/runner-nostd.rs b/tests/runtime-new/strings-simple/runner-nostd.rs new file mode 100644 index 000000000..939496c7b --- /dev/null +++ b/tests/runtime-new/strings-simple/runner-nostd.rs @@ -0,0 +1,18 @@ +//@ args = ['--std-feature'] + +#![no_std] + +extern crate alloc; + +use alloc::string::String; + +include!(env!("BINDINGS")); + +fn main() { + // Test the argument is `&str` + cat::foo("hello"); + + // Test the return type is `String` + let t: String = cat::bar(); + assert_eq!(t, "world"); +} diff --git a/tests/runtime-new/strings-simple/runner-std.rs b/tests/runtime-new/strings-simple/runner-std.rs new file mode 100644 index 000000000..f2c664c2f --- /dev/null +++ b/tests/runtime-new/strings-simple/runner-std.rs @@ -0,0 +1,10 @@ +include!(env!("BINDINGS")); + +fn main() { + // Test the argument is `&str` + cat::foo("hello"); + + // Test the return type is `String` + let t: String = cat::bar(); + assert_eq!(t, "world"); +} diff --git a/tests/runtime-new/strings-simple/test.rs b/tests/runtime-new/strings-simple/test.rs new file mode 100644 index 000000000..dca7e8fab --- /dev/null +++ b/tests/runtime-new/strings-simple/test.rs @@ -0,0 +1,15 @@ +include!(env!("BINDINGS")); + +struct Test; + +export!(Test); + +impl exports::cat::Guest for Test { + fn foo(x: String) { + assert_eq!(x, "hello"); + } + + fn bar() -> String { + "world".into() + } +} diff --git a/tests/runtime-new/strings-simple/test.wit b/tests/runtime-new/strings-simple/test.wit new file mode 100644 index 000000000..96e70a1a1 --- /dev/null +++ b/tests/runtime-new/strings-simple/test.wit @@ -0,0 +1,15 @@ +package my:strings; + +world runner { + import cat: interface { + foo: func(x: string); + bar: func() -> string; + } +} + +world test { + export cat: interface { + foo: func(x: string); + bar: func() -> string; + } +} diff --git a/tests/runtime-new/symbol-conflicts/runner.rs b/tests/runtime-new/symbol-conflicts/runner.rs new file mode 100644 index 000000000..332da6c25 --- /dev/null +++ b/tests/runtime-new/symbol-conflicts/runner.rs @@ -0,0 +1,8 @@ +include!(env!("BINDINGS")); + +fn main() { + my::inline::foo1::foo(); + my::inline::foo2::foo(); + my::inline::bar1::bar(); + my::inline::bar2::bar(); +} diff --git a/tests/runtime-new/symbol-conflicts/test.rs b/tests/runtime-new/symbol-conflicts/test.rs new file mode 100644 index 000000000..7f9c74ac9 --- /dev/null +++ b/tests/runtime-new/symbol-conflicts/test.rs @@ -0,0 +1,25 @@ +include!(env!("BINDINGS")); + +struct Component; + +impl exports::my::inline::foo1::Guest for Component { + fn foo() {} +} + +impl exports::my::inline::foo2::Guest for Component { + fn foo() {} +} + +impl exports::my::inline::bar1::Guest for Component { + fn bar() -> String { + String::new() + } +} + +impl exports::my::inline::bar2::Guest for Component { + fn bar() -> String { + String::new() + } +} + +export!(Component); diff --git a/tests/runtime-new/symbol-conflicts/test.wit b/tests/runtime-new/symbol-conflicts/test.wit new file mode 100644 index 000000000..abc8352f0 --- /dev/null +++ b/tests/runtime-new/symbol-conflicts/test.wit @@ -0,0 +1,31 @@ +package my:inline; + +interface foo1 { + foo: func(); +} + +interface foo2 { + foo: func(); +} + +interface bar1 { + bar: func() -> string; +} + +interface bar2 { + bar: func() -> string; +} + +world test { + export foo1; + export foo2; + export bar1; + export bar2; +} + +world runner { + import foo1; + import foo2; + import bar1; + import bar2; +} diff --git a/tests/runtime-new/unused-types/runner.rs b/tests/runtime-new/unused-types/runner.rs new file mode 100644 index 000000000..48c2c11ba --- /dev/null +++ b/tests/runtime-new/unused-types/runner.rs @@ -0,0 +1,14 @@ +//@ args = '--generate-unused-types' + +#[expect(unused_imports)] +use foo::bar::component::UnusedEnum as _; +#[expect(unused_imports)] +use foo::bar::component::UnusedRecord as _; +#[expect(unused_imports)] +use foo::bar::component::UnusedVariant as _; + +include!(env!("BINDINGS")); + +fn main() { + foo::bar::component::foo(); +} diff --git a/tests/runtime-new/unused-types/test.rs b/tests/runtime-new/unused-types/test.rs new file mode 100644 index 000000000..e4e0d5d6b --- /dev/null +++ b/tests/runtime-new/unused-types/test.rs @@ -0,0 +1,19 @@ +//@ args = '--generate-unused-types' + +include!(env!("BINDINGS")); + +use exports::foo::bar::component::Guest; +#[expect(unused_imports)] +use exports::foo::bar::component::UnusedEnum as _; +#[expect(unused_imports)] +use exports::foo::bar::component::UnusedRecord as _; +#[expect(unused_imports)] +use exports::foo::bar::component::UnusedVariant as _; + +struct Component; + +export!(Component); + +impl Guest for Component { + fn foo() {} +} diff --git a/tests/runtime-new/unused-types/test.wit b/tests/runtime-new/unused-types/test.wit new file mode 100644 index 000000000..72a956d85 --- /dev/null +++ b/tests/runtime-new/unused-types/test.wit @@ -0,0 +1,23 @@ +package foo:bar; + +world test { + export component; +} +world runner { + import component; +} + +interface component { + variant unused-variant { + %enum(unused-enum), + %record(unused-record) + } + enum unused-enum { + unused + } + record unused-record { + x: u32 + } + + foo: func(); +} diff --git a/tests/runtime/lists/wasm.c b/tests/runtime/lists/wasm.c deleted file mode 100644 index bcac39375..000000000 --- a/tests/runtime/lists/wasm.c +++ /dev/null @@ -1,382 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -uint32_t exports_lists_allocated_bytes(void) { - return 0; -} - -void exports_lists_test_imports() { - { - uint8_t list[] = {}; - lists_list_u8_t a; - a.ptr = list; - a.len = 0; - test_lists_test_empty_list_param(&a); - } - - { - lists_string_t a; - lists_string_set(&a, ""); - test_lists_test_empty_string_param(&a); - } - - { - lists_list_u8_t a; - test_lists_test_empty_list_result(&a); - assert(a.len == 0); - } - - { - lists_string_t a; - test_lists_test_empty_string_result(&a); - assert(a.len == 0); - } - - { - uint8_t list[] = {1, 2, 3, 4}; - lists_list_u8_t a; - a.ptr = list; - a.len = 4; - test_lists_test_list_param(&a); - } - - { - lists_string_t a; - lists_string_set(&a, "foo"); - test_lists_test_list_param2(&a); - } - - { - lists_string_t list[3]; - lists_string_set(&list[0], "foo"); - lists_string_set(&list[1], "bar"); - lists_string_set(&list[2], "baz"); - lists_list_string_t a; - a.ptr = list; - a.len = 3; - test_lists_test_list_param3(&a); - } - - { - lists_string_t list1[2]; - lists_string_t list2[1]; - lists_string_set(&list1[0], "foo"); - lists_string_set(&list1[1], "bar"); - lists_string_set(&list2[0], "baz"); - lists_list_list_string_t a; - a.ptr[0].len = 2; - a.ptr[0].ptr = list1; - a.ptr[1].len = 1; - a.ptr[1].ptr = list2; - a.len = 2; - test_lists_test_list_param4(&a); - } - - { - lists_tuple3_u8_u32_u8_t data[2]; - data[0].f0 = 1; - data[0].f1 = 2; - data[0].f2 = 3; - data[1].f0 = 4; - data[1].f1 = 5; - data[1].f2 = 6; - lists_list_tuple3_u8_u32_u8_t a; - a.len = 2; - a.ptr = data; - test_lists_test_list_param5(&a); - } - - { - lists_list_u8_t a; - test_lists_test_list_result(&a); - assert(a.len == 5); - assert(memcmp(a.ptr, "\x01\x02\x03\x04\x05", 5) == 0); - lists_list_u8_free(&a); - } - - { - lists_string_t a; - test_lists_test_list_result2(&a); - assert(a.len == 6); - assert(memcmp(a.ptr, "hello!", 6) == 0); - lists_string_free(&a); - } - - { - lists_list_string_t a; - test_lists_test_list_result3(&a); - assert(a.len == 2); - assert(a.ptr[0].len == 6); - assert(a.ptr[1].len == 6); - assert(memcmp(a.ptr[0].ptr, "hello,", 6) == 0); - assert(memcmp(a.ptr[1].ptr, "world!", 6) == 0); - lists_list_string_free(&a); - } - - { - lists_list_u8_t a, b; - a.len = 0; - a.ptr = (unsigned char*) ""; - test_lists_test_list_roundtrip(&a, &b); - assert(b.len == a.len); - assert(memcmp(b.ptr, a.ptr, a.len) == 0); - lists_list_u8_free(&b); - - a.len = 1; - a.ptr = (unsigned char*) "x"; - test_lists_test_list_roundtrip(&a, &b); - assert(b.len == a.len); - assert(memcmp(b.ptr, a.ptr, a.len) == 0); - lists_list_u8_free(&b); - - a.len = 5; - a.ptr = (unsigned char*) "hello"; - test_lists_test_list_roundtrip(&a, &b); - assert(b.len == a.len); - assert(memcmp(b.ptr, a.ptr, a.len) == 0); - lists_list_u8_free(&b); - } - - { - lists_string_t a, b; - lists_string_set(&a, "x"); - test_lists_test_string_roundtrip(&a, &b); - assert(b.len == a.len); - assert(memcmp(b.ptr, a.ptr, a.len) == 0); - lists_string_free(&b); - - lists_string_set(&a, ""); - test_lists_test_string_roundtrip(&a, &b); - assert(b.len == a.len); - lists_string_free(&b); - - lists_string_set(&a, "hello"); - test_lists_test_string_roundtrip(&a, &b); - assert(b.len == a.len); - assert(memcmp(b.ptr, a.ptr, a.len) == 0); - lists_string_free(&b); - - lists_string_set(&a, "hello ⚑ world"); - test_lists_test_string_roundtrip(&a, &b); - assert(b.len == a.len); - assert(memcmp(b.ptr, a.ptr, a.len) == 0); - lists_string_free(&b); - } - - { - uint8_t u8[2] = {0, UCHAR_MAX}; - int8_t s8[2] = {SCHAR_MIN, SCHAR_MAX}; - lists_list_u8_t list_u8 = { u8, 2 }; - lists_list_s8_t list_s8 = { s8, 2 }; - lists_tuple2_list_u8_list_s8_t ret; - test_lists_test_list_minmax8(&list_u8, &list_s8, &ret); - assert(ret.f0.len == 2 && ret.f0.ptr[0] == 0 && ret.f0.ptr[1] == UCHAR_MAX); - assert(ret.f1.len == 2 && ret.f1.ptr[0] == SCHAR_MIN && ret.f1.ptr[1] == SCHAR_MAX); - lists_list_u8_free(&ret.f0); - lists_list_s8_free(&ret.f1); - } - - { - uint16_t u16[2] = {0, USHRT_MAX}; - int16_t s16[2] = {SHRT_MIN, SHRT_MAX}; - lists_list_u16_t list_u16 = { u16, 2 }; - lists_list_s16_t list_s16 = { s16, 2 }; - lists_tuple2_list_u16_list_s16_t ret; - test_lists_test_list_minmax16(&list_u16, &list_s16, &ret); - assert(ret.f0.len == 2 && ret.f0.ptr[0] == 0 && ret.f0.ptr[1] == USHRT_MAX); - assert(ret.f1.len == 2 && ret.f1.ptr[0] == SHRT_MIN && ret.f1.ptr[1] == SHRT_MAX); - lists_list_u16_free(&ret.f0); - lists_list_s16_free(&ret.f1); - } - - { - uint32_t u32[2] = {0, UINT_MAX}; - int32_t s32[2] = {INT_MIN, INT_MAX}; - lists_list_u32_t list_u32 = { u32, 2 }; - lists_list_s32_t list_s32 = { s32, 2 }; - lists_tuple2_list_u32_list_s32_t ret; - test_lists_test_list_minmax32(&list_u32, &list_s32, &ret); - assert(ret.f0.len == 2 && ret.f0.ptr[0] == 0 && ret.f0.ptr[1] == UINT_MAX); - assert(ret.f1.len == 2 && ret.f1.ptr[0] == INT_MIN && ret.f1.ptr[1] == INT_MAX); - lists_list_u32_free(&ret.f0); - lists_list_s32_free(&ret.f1); - } - - { - uint64_t u64[2] = {0, ULLONG_MAX}; - int64_t s64[2] = {LLONG_MIN, LLONG_MAX}; - lists_list_u64_t list_u64 = { u64, 2 }; - lists_list_s64_t list_s64 = { s64, 2 }; - lists_tuple2_list_u64_list_s64_t ret; - test_lists_test_list_minmax64(&list_u64, &list_s64, &ret); - assert(ret.f0.len == 2 && ret.f0.ptr[0] == 0 && ret.f0.ptr[1] == ULLONG_MAX); - assert(ret.f1.len == 2 && ret.f1.ptr[0] == LLONG_MIN && ret.f1.ptr[1] == LLONG_MAX); - lists_list_u64_free(&ret.f0); - lists_list_s64_free(&ret.f1); - } - - { - float f32[4] = {-FLT_MAX, FLT_MAX, -INFINITY, INFINITY}; - double f64[4] = {-DBL_MAX, DBL_MAX, -INFINITY, INFINITY}; - lists_list_f32_t list_f32 = { f32, 4 }; - lists_list_f64_t list_f64 = { f64, 4 }; - lists_tuple2_list_f32_list_f64_t ret; - test_lists_test_list_minmax_float(&list_f32, &list_f64, &ret); - assert(ret.f0.len == 4 && ret.f0.ptr[0] == -FLT_MAX && ret.f0.ptr[1] == FLT_MAX); - assert(ret.f0.ptr[2] == -INFINITY && ret.f0.ptr[3] == INFINITY); - assert(ret.f1.len == 4 && ret.f1.ptr[0] == -DBL_MAX && ret.f1.ptr[1] == DBL_MAX); - assert(ret.f1.ptr[2] == -INFINITY && ret.f1.ptr[3] == INFINITY); - lists_list_f32_free(&ret.f0); - lists_list_f64_free(&ret.f1); - } -} - -void exports_test_lists_test_empty_list_param(lists_list_u8_t *a) { - assert(a->len == 0); -} - -void exports_test_lists_test_empty_string_param(lists_string_t *a) { - assert(a->len == 0); -} - -void exports_test_lists_test_empty_list_result(lists_list_u8_t *ret0) { - ret0->ptr = 0; - ret0->len = 0; -} - -void exports_test_lists_test_empty_string_result(lists_string_t *ret0) { - ret0->ptr = 0; - ret0->len = 0; -} - -void exports_test_lists_test_list_param(lists_list_u8_t *a) { - assert(a->len == 4); - assert(a->ptr[0] == 1); - assert(a->ptr[1] == 2); - assert(a->ptr[2] == 3); - assert(a->ptr[3] == 4); - lists_list_u8_free(a); -} - -void exports_test_lists_test_list_param2(lists_string_t *a) { - assert(a->len == 3); - assert(a->ptr[0] == 'f'); - assert(a->ptr[1] == 'o'); - assert(a->ptr[2] == 'o'); - lists_string_free(a); -} - -void exports_test_lists_test_list_param3(lists_list_string_t *a) { - assert(a->len == 3); - assert(a->ptr[0].len == 3); - assert(a->ptr[0].ptr[0] == 'f'); - assert(a->ptr[0].ptr[1] == 'o'); - assert(a->ptr[0].ptr[2] == 'o'); - - assert(a->ptr[1].len == 3); - assert(a->ptr[1].ptr[0] == 'b'); - assert(a->ptr[1].ptr[1] == 'a'); - assert(a->ptr[1].ptr[2] == 'r'); - - assert(a->ptr[2].len == 3); - assert(a->ptr[2].ptr[0] == 'b'); - assert(a->ptr[2].ptr[1] == 'a'); - assert(a->ptr[2].ptr[2] == 'z'); - - lists_list_string_free(a); -} - -void exports_test_lists_test_list_param4(lists_list_list_string_t *a) { - assert(a->len == 2); - assert(a->ptr[0].len == 2); - assert(a->ptr[1].len == 1); - - assert(a->ptr[0].ptr[0].len == 3); - assert(a->ptr[0].ptr[0].ptr[0] == 'f'); - assert(a->ptr[0].ptr[0].ptr[1] == 'o'); - assert(a->ptr[0].ptr[0].ptr[2] == 'o'); - - assert(a->ptr[0].ptr[1].len == 3); - assert(a->ptr[0].ptr[1].ptr[0] == 'b'); - assert(a->ptr[0].ptr[1].ptr[1] == 'a'); - assert(a->ptr[0].ptr[1].ptr[2] == 'r'); - - assert(a->ptr[1].ptr[0].len == 3); - assert(a->ptr[1].ptr[0].ptr[0] == 'b'); - assert(a->ptr[1].ptr[0].ptr[1] == 'a'); - assert(a->ptr[1].ptr[0].ptr[2] == 'z'); - - lists_list_list_string_free(a); -} - -void exports_test_lists_test_list_param_large(lists_list_string_t *a) { - assert(a->len == 1000); - lists_list_string_free(a); -} - -void exports_test_lists_test_list_param5(lists_list_tuple3_u8_u32_u8_t *a) { - assert(a->len == 2); - assert(a->ptr[0].f0 == 1); - assert(a->ptr[0].f1 == 2); - assert(a->ptr[0].f2 == 3); - assert(a->ptr[1].f0 == 4); - assert(a->ptr[1].f1 == 5); - assert(a->ptr[1].f2 == 6); - lists_list_tuple3_u8_u32_u8_free(a); -} - -void exports_test_lists_test_list_result(lists_list_u8_t *ret0) { - ret0->ptr = (uint8_t *) malloc(5); - ret0->len = 5; - ret0->ptr[0] = 1; - ret0->ptr[1] = 2; - ret0->ptr[2] = 3; - ret0->ptr[3] = 4; - ret0->ptr[4] = 5; -} - -void exports_test_lists_test_list_result2(lists_string_t *ret0) { - lists_string_dup(ret0, "hello!"); -} - -void exports_test_lists_test_list_result3(lists_list_string_t *ret0) { - ret0->len = 2; - ret0->ptr = (lists_string_t *) malloc(2 * sizeof(lists_string_t)); - - lists_string_dup(&ret0->ptr[0], "hello,"); - lists_string_dup(&ret0->ptr[1], "world!"); -} - -void exports_test_lists_test_list_roundtrip(lists_list_u8_t *a, lists_list_u8_t *ret0) { - *ret0 = *a; -} - -void exports_test_lists_test_string_roundtrip(lists_string_t *a, lists_string_t *ret0) { - *ret0 = *a; -} - -void exports_test_lists_test_list_minmax8(lists_list_u8_t *a, lists_list_s8_t *b, lists_tuple2_list_u8_list_s8_t *ret) { - assert(0); // unimplemented -} - -void exports_test_lists_test_list_minmax16(lists_list_u16_t *a, lists_list_s16_t *b, lists_tuple2_list_u16_list_s16_t *ret) { - assert(0); // unimplemented -} - -void exports_test_lists_test_list_minmax32(lists_list_u32_t *a, lists_list_s32_t *b, lists_tuple2_list_u32_list_s32_t *ret) { - assert(0); // unimplemented -} - -void exports_test_lists_test_list_minmax64(lists_list_u64_t *a, lists_list_s64_t *b, lists_tuple2_list_u64_list_s64_t *ret) { - assert(0); // unimplemented -} - -void exports_test_lists_test_list_minmax_float(lists_list_f32_t *a, lists_list_f64_t *b, lists_tuple2_list_f32_list_f64_t *ret) { - assert(0); // unimplemented -} diff --git a/tests/runtime/numbers/wasm.c b/tests/runtime/numbers/wasm.c deleted file mode 100644 index bc62d3b9c..000000000 --- a/tests/runtime/numbers/wasm.c +++ /dev/null @@ -1,112 +0,0 @@ -#include -#include -#include -#include - -uint8_t exports_test_numbers_test_roundtrip_u8(uint8_t a) { - return a; -} - -int8_t exports_test_numbers_test_roundtrip_s8(int8_t a) { - return a; -} - -uint16_t exports_test_numbers_test_roundtrip_u16(uint16_t a) { - return a; -} - -int16_t exports_test_numbers_test_roundtrip_s16(int16_t a) { - return a; -} - -uint32_t exports_test_numbers_test_roundtrip_u32(uint32_t a) { - return a; -} - -int32_t exports_test_numbers_test_roundtrip_s32(int32_t a) { - return a; -} - -uint64_t exports_test_numbers_test_roundtrip_u64(uint64_t a) { - return a; -} - -int64_t exports_test_numbers_test_roundtrip_s64(int64_t a) { - return a; -} - -float exports_test_numbers_test_roundtrip_f32(float a) { - return a; -} - -double exports_test_numbers_test_roundtrip_f64(double a) { - return a; -} - -uint32_t exports_test_numbers_test_roundtrip_char(uint32_t a) { - return a; -} - -static uint32_t SCALAR = 0; - -void exports_test_numbers_test_set_scalar(uint32_t a) { - SCALAR = a; -} - -uint32_t exports_test_numbers_test_get_scalar(void) { - return SCALAR; -} - - -void exports_numbers_test_imports() { - assert(test_numbers_test_roundtrip_u8(1) == 1); - assert(test_numbers_test_roundtrip_u8(0) == 0); - assert(test_numbers_test_roundtrip_u8(UCHAR_MAX) == UCHAR_MAX); - - assert(test_numbers_test_roundtrip_s8(1) == 1); - assert(test_numbers_test_roundtrip_s8(SCHAR_MIN) == SCHAR_MIN); - assert(test_numbers_test_roundtrip_s8(SCHAR_MAX) == SCHAR_MAX); - - assert(test_numbers_test_roundtrip_u16(1) == 1); - assert(test_numbers_test_roundtrip_u16(0) == 0); - assert(test_numbers_test_roundtrip_u16(USHRT_MAX) == USHRT_MAX); - - assert(test_numbers_test_roundtrip_s16(1) == 1); - assert(test_numbers_test_roundtrip_s16(SHRT_MIN) == SHRT_MIN); - assert(test_numbers_test_roundtrip_s16(SHRT_MAX) == SHRT_MAX); - - assert(test_numbers_test_roundtrip_u32(1) == 1); - assert(test_numbers_test_roundtrip_u32(0) == 0); - assert(test_numbers_test_roundtrip_u32(UINT_MAX) == UINT_MAX); - - assert(test_numbers_test_roundtrip_s32(1) == 1); - assert(test_numbers_test_roundtrip_s32(INT_MIN) == INT_MIN); - assert(test_numbers_test_roundtrip_s32(INT_MAX) == INT_MAX); - - assert(test_numbers_test_roundtrip_u64(1) == 1); - assert(test_numbers_test_roundtrip_u64(0) == 0); - assert(test_numbers_test_roundtrip_u64(ULONG_MAX) == ULONG_MAX); - - assert(test_numbers_test_roundtrip_s64(1) == 1); - assert(test_numbers_test_roundtrip_s64(LONG_MIN) == LONG_MIN); - assert(test_numbers_test_roundtrip_s64(LONG_MAX) == LONG_MAX); - - assert(test_numbers_test_roundtrip_f32(1.0) == 1.0); - assert(test_numbers_test_roundtrip_f32(INFINITY) == INFINITY); - assert(test_numbers_test_roundtrip_f32(-INFINITY) == -INFINITY); - assert(isnan(test_numbers_test_roundtrip_f32(NAN))); - - assert(test_numbers_test_roundtrip_f64(1.0) == 1.0); - assert(test_numbers_test_roundtrip_f64(INFINITY) == INFINITY); - assert(test_numbers_test_roundtrip_f64(-INFINITY) == -INFINITY); - assert(isnan(test_numbers_test_roundtrip_f64(NAN))); - - assert(test_numbers_test_roundtrip_char('a') == 'a'); - assert(test_numbers_test_roundtrip_char(' ') == ' '); - assert(test_numbers_test_roundtrip_char(U'🚩') == U'🚩'); - - test_numbers_test_set_scalar(2); - assert(test_numbers_test_get_scalar() == 2); - test_numbers_test_set_scalar(4); - assert(test_numbers_test_get_scalar() == 4); -} diff --git a/tests/runtime/numbers/wasm.rs b/tests/runtime/numbers/wasm.rs deleted file mode 100644 index 5769241f8..000000000 --- a/tests/runtime/numbers/wasm.rs +++ /dev/null @@ -1,121 +0,0 @@ -wit_bindgen::generate!({ - path: "../../tests/runtime/numbers", -}); - -use std::sync::atomic::{AtomicU32, Ordering::SeqCst}; - -static SCALAR: AtomicU32 = AtomicU32::new(0); - -struct Component; - -export!(Component); - -impl Guest for Component { - fn test_imports() { - use test::numbers::test::*; - assert_eq!(roundtrip_u8(1), 1); - assert_eq!(roundtrip_u8(u8::min_value()), u8::min_value()); - assert_eq!(roundtrip_u8(u8::max_value()), u8::max_value()); - - assert_eq!(roundtrip_s8(1), 1); - assert_eq!(roundtrip_s8(i8::min_value()), i8::min_value()); - assert_eq!(roundtrip_s8(i8::max_value()), i8::max_value()); - - assert_eq!(roundtrip_u16(1), 1); - assert_eq!(roundtrip_u16(u16::min_value()), u16::min_value()); - assert_eq!(roundtrip_u16(u16::max_value()), u16::max_value()); - - assert_eq!(roundtrip_s16(1), 1); - assert_eq!(roundtrip_s16(i16::min_value()), i16::min_value()); - assert_eq!(roundtrip_s16(i16::max_value()), i16::max_value()); - - assert_eq!(roundtrip_u32(1), 1); - assert_eq!(roundtrip_u32(u32::min_value()), u32::min_value()); - assert_eq!(roundtrip_u32(u32::max_value()), u32::max_value()); - - assert_eq!(roundtrip_s32(1), 1); - assert_eq!(roundtrip_s32(i32::min_value()), i32::min_value()); - assert_eq!(roundtrip_s32(i32::max_value()), i32::max_value()); - - assert_eq!(roundtrip_u64(1), 1); - assert_eq!(roundtrip_u64(u64::min_value()), u64::min_value()); - assert_eq!(roundtrip_u64(u64::max_value()), u64::max_value()); - - assert_eq!(roundtrip_s64(1), 1); - assert_eq!(roundtrip_s64(i64::min_value()), i64::min_value()); - assert_eq!(roundtrip_s64(i64::max_value()), i64::max_value()); - - assert_eq!(roundtrip_f32(1.0), 1.0); - assert_eq!(roundtrip_f32(f32::INFINITY), f32::INFINITY); - assert_eq!(roundtrip_f32(f32::NEG_INFINITY), f32::NEG_INFINITY); - assert!(roundtrip_f32(f32::NAN).is_nan()); - - assert_eq!(roundtrip_f64(1.0), 1.0); - assert_eq!(roundtrip_f64(f64::INFINITY), f64::INFINITY); - assert_eq!(roundtrip_f64(f64::NEG_INFINITY), f64::NEG_INFINITY); - assert!(roundtrip_f64(f64::NAN).is_nan()); - - assert_eq!(roundtrip_char('a'), 'a'); - assert_eq!(roundtrip_char(' '), ' '); - assert_eq!(roundtrip_char('🚩'), '🚩'); - - set_scalar(2); - assert_eq!(get_scalar(), 2); - set_scalar(4); - assert_eq!(get_scalar(), 4); - } -} - -impl exports::test::numbers::test::Guest for Component { - fn roundtrip_u8(a: u8) -> u8 { - a - } - - fn roundtrip_s8(a: i8) -> i8 { - a - } - - fn roundtrip_u16(a: u16) -> u16 { - a - } - - fn roundtrip_s16(a: i16) -> i16 { - a - } - - fn roundtrip_u32(a: u32) -> u32 { - a - } - - fn roundtrip_s32(a: i32) -> i32 { - a - } - - fn roundtrip_u64(a: u64) -> u64 { - a - } - - fn roundtrip_s64(a: i64) -> i64 { - a - } - - fn roundtrip_f32(a: f32) -> f32 { - a - } - - fn roundtrip_f64(a: f64) -> f64 { - a - } - - fn roundtrip_char(a: char) -> char { - a - } - - fn set_scalar(val: u32) { - SCALAR.store(val, SeqCst) - } - - fn get_scalar() -> u32 { - SCALAR.load(SeqCst) - } -} From 54f00cc74001e70a801a6b925ee003f82e924205 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 12 Mar 2025 16:32:08 -0700 Subject: [PATCH 13/14] Complete support for rust 2024 edition (#1206) * rust codegen tests: check with both edition 2021 and 2024 * Fixes to codegen to support 2024 edition make various extern C blocks unsafe, and their call sites unsafe all rust codegen tests pass for both edition 2021 and 2024. --------- Co-authored-by: Alex Crichton --- crates/rust/src/bindgen.rs | 19 ++++++------ crates/rust/src/interface.rs | 20 ++++++------- crates/rust/src/lib.rs | 2 +- crates/test/src/rust.rs | 56 ++++++++++++++++++++++-------------- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 1089ddb0b..222efb107 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -98,13 +98,13 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { " #[cfg(target_arch = \"wasm32\")] #[link(wasm_import_module = \"{module_name}\")] - extern \"C\" {{ + unsafe extern \"C\" {{ #[link_name = \"{name}\"] fn wit_import{tmp}{sig}; }} #[cfg(not(target_arch = \"wasm32\"))] - extern \"C\" fn wit_import{tmp}{sig} {{ unreachable!() }} + unsafe extern \"C\" fn wit_import{tmp}{sig} {{ unreachable!() }} " ); format!("wit_import{tmp}") @@ -464,21 +464,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = if is_own { let name = self.r#gen.type_path(dealiased_resource, true); - format!("{name}::from_handle({op} as u32)") + format!("unsafe {{ {name}::from_handle({op} as u32) }}") } else if self.r#gen.is_exported_resource(*resource) { let name = resolve.types[*resource] .name .as_deref() .unwrap() .to_upper_camel_case(); - format!("{name}Borrow::lift({op} as u32 as usize)") + format!("unsafe {{ {name}Borrow::lift({op} as u32 as usize) }}") } else { let tmp = format!("handle{}", self.tmp()); self.handle_decls.push(format!("let {tmp};")); let name = self.r#gen.type_path(dealiased_resource, true); format!( "{{\n - {tmp} = {name}::from_handle({op} as u32); + {tmp} = unsafe {{ {name}::from_handle({op} as u32) }}; &{tmp} }}" ) @@ -509,8 +509,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { .unwrap(); let path = self.r#gen.path_to_root(); results.push(format!( - "{async_support}::FutureReader::from_handle_and_vtable\ - ({op} as u32, &{path}wit_future::vtable{ordinal}::VTABLE)" + "unsafe {{ {async_support}::FutureReader::from_handle_and_vtable\ + ({op} as u32, &{path}wit_future::vtable{ordinal}::VTABLE) }}" )) } @@ -897,10 +897,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str("let ret = "); results.push("ret".to_string()); } + self.push_str("unsafe { "); self.push_str(&func); self.push_str("("); self.push_str(&operands.join(", ")); - self.push_str(");\n"); + self.push_str(") };\n"); } Instruction::AsyncCallWasm { name, .. } => { @@ -1027,7 +1028,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!( self.src, "\ - {func}({}); + unsafe {{ {func}({}) }}; ", operands.join(", ") ); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 7024679ee..4430dcfdf 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -224,11 +224,11 @@ unsafe fn _resource_new(val: *mut u8) -> u32 #[cfg(target_arch = "wasm32")] {{ #[link(wasm_import_module = "[export]{module}")] - extern "C" {{ + unsafe extern "C" {{ #[link_name = "[resource-new]{resource_name}"] fn new(_: *mut u8) -> u32; }} - new(val) + unsafe {{ new(val) }} }} }} @@ -245,7 +245,7 @@ fn _resource_rep(handle: u32) -> *mut u8 #[cfg(target_arch = "wasm32")] {{ #[link(wasm_import_module = "[export]{module}")] - extern "C" {{ + unsafe extern "C" {{ #[link_name = "[resource-rep]{resource_name}"] fn rep(_: u32) -> *mut u8; }} @@ -602,7 +602,7 @@ pub mod vtable{ordinal} {{ #[cfg(target_arch = "wasm32")] #[link(wasm_import_module = "{module}")] - extern "C" {{ + unsafe extern "C" {{ #[link_name = "[future-new-{index}]{func_name}"] fn new() -> u32; #[link_name = "[future-cancel-write-{index}]{func_name}"] @@ -784,7 +784,7 @@ pub mod vtable{ordinal} {{ #[cfg(target_arch = "wasm32")] #[link(wasm_import_module = "{module}")] - extern "C" {{ + unsafe extern "C" {{ #[link_name = "[stream-new-{index}]{func_name}"] fn new() -> u32; #[link_name = "[stream-cancel-write-{index}]{func_name}"] @@ -2472,7 +2472,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { #[doc(hidden)] pub unsafe fn from_handle(handle: u32) -> Self {{ Self {{ - handle: {resource}::from_handle(handle), + handle: unsafe {{ {resource}::from_handle(handle) }}, }} }} @@ -2545,7 +2545,7 @@ impl {camel} {{ #[doc(hidden)] pub unsafe fn from_handle(handle: u32) -> Self {{ Self {{ - handle: {resource}::from_handle(handle), + handle: unsafe {{ {resource}::from_handle(handle) }}, }} }} @@ -2578,7 +2578,7 @@ impl {camel} {{ #[doc(hidden)] pub unsafe fn dtor(handle: *mut u8) {{ Self::type_guard::(); - let _ = {box_path}::from_raw(handle as *mut _{camel}Rep); + let _ = unsafe {{ {box_path}::from_raw(handle as *mut _{camel}Rep) }}; }} fn as_ptr(&self) -> *mut _{camel}Rep {{ @@ -2637,12 +2637,12 @@ impl<'a> {camel}Borrow<'a>{{ #[cfg(target_arch = "wasm32")] {{ #[link(wasm_import_module = "{wasm_import_module}")] - extern "C" {{ + unsafe extern "C" {{ #[link_name = "[resource-drop]{name}"] fn drop(_: u32); }} - drop(_handle); + unsafe {{ drop(_handle) }}; }} }} }} diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 2e3bcbdff..8eeb7f33c 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -589,7 +589,7 @@ pub unsafe fn invalid_enum_discriminant() -> T { if cfg!(debug_assertions) { panic!(\"invalid enum discriminant\") } else { - core::hint::unreachable_unchecked() + unsafe { core::hint::unreachable_unchecked() } } } ", diff --git a/crates/test/src/rust.rs b/crates/test/src/rust.rs index b22959ca4..0bed20c20 100644 --- a/crates/test/src/rust.rs +++ b/crates/test/src/rust.rs @@ -142,7 +142,7 @@ path = 'lib.rs' } fn compile(&self, runner: &Runner<'_>, compile: &Compile) -> Result<()> { - let mut cmd = runner.rustc(); + let mut cmd = runner.rustc(Edition::E2021); // If this rust target doesn't natively produce a component then place // the compiler output in a temporary location which is componentized @@ -181,15 +181,21 @@ path = 'lib.rs' } fn verify(&self, runner: &Runner<'_>, verify: &Verify<'_>) -> Result<()> { - let mut cmd = runner.rustc(); let bindings = verify .bindings_dir .join(format!("{}.rs", verify.world.to_snake_case())); - cmd.arg(&bindings) - .arg("--crate-type=rlib") - .arg("-o") - .arg(verify.artifacts_dir.join("tmp")); - runner.run_command(&mut cmd)?; + let test_edition = |edition: Edition| -> Result<()> { + let mut cmd = runner.rustc(edition); + cmd.arg(&bindings) + .arg("--crate-type=rlib") + .arg("-o") + .arg(verify.artifacts_dir.join("tmp")); + runner.run_command(&mut cmd)?; + Ok(()) + }; + + test_edition(Edition::E2021)?; + test_edition(Edition::E2024)?; // If bindings are generated in `#![no_std]` mode then verify that it // compiles as such. @@ -208,7 +214,7 @@ include!(env!("BINDINGS")); mod core {} "#, )?; - let mut cmd = runner.rustc(); + let mut cmd = runner.rustc(Edition::E2021); cmd.arg(&no_std_root) .env("BINDINGS", &bindings) .arg("--crate-type=rlib") @@ -220,23 +226,31 @@ mod core {} } } +enum Edition { + E2021, + E2024, +} + impl Runner<'_> { - fn rustc(&self) -> Command { + fn rustc(&self, edition: Edition) -> Command { let state = self.rust_state.as_ref().unwrap(); let opts = &self.opts.rust; let mut cmd = Command::new("rustc"); - cmd.arg("--edition=2021") - .arg(&format!( - "--extern=wit_bindgen={}", - state.wit_bindgen_rlib.display() - )) - .arg(&format!( - "--extern=futures={}", - state.futures_rlib.display() - )) - .arg("--target") - .arg(&opts.rust_target) - .arg("-Cdebuginfo=1"); + cmd.arg(match edition { + Edition::E2021 => "--edition=2021", + Edition::E2024 => "--edition=2024", + }) + .arg(&format!( + "--extern=wit_bindgen={}", + state.wit_bindgen_rlib.display() + )) + .arg(&format!( + "--extern=futures={}", + state.futures_rlib.display() + )) + .arg("--target") + .arg(&opts.rust_target) + .arg("-Cdebuginfo=1"); for dep in state.wit_bindgen_deps.iter() { cmd.arg(&format!("-Ldependency={}", dep.display())); } From d8994a329a74f6eb96c080b42765e42af6ef5be6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 01:35:08 +0000 Subject: [PATCH 14/14] Release wit-bindgen 0.41.0 (#1208) [automatically-tag-and-release-this-commit] Co-authored-by: Auto Release Process --- Cargo.lock | 22 +++++++++--------- Cargo.toml | 18 +++++++------- crates/guest-rust/Cargo.toml | 4 ++-- crates/guest-rust/rt/src/cabi_realloc.c | 4 ++-- crates/guest-rust/rt/src/cabi_realloc.o | Bin 261 -> 261 bytes crates/guest-rust/rt/src/cabi_realloc.rs | 2 +- .../rt/src/libwit_bindgen_cabi_realloc.a | Bin 412 -> 412 bytes 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42cb6a723..82afbdf98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2704,7 +2704,7 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.40.0" +version = "0.41.0" dependencies = [ "wit-bindgen-rt", "wit-bindgen-rust-macro", @@ -2712,7 +2712,7 @@ dependencies = [ [[package]] name = "wit-bindgen-c" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "clap", @@ -2725,7 +2725,7 @@ dependencies = [ [[package]] name = "wit-bindgen-cli" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "clap", @@ -2748,7 +2748,7 @@ dependencies = [ [[package]] name = "wit-bindgen-core" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "heck 0.5.0", @@ -2757,7 +2757,7 @@ dependencies = [ [[package]] name = "wit-bindgen-csharp" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "clap", @@ -2774,7 +2774,7 @@ dependencies = [ [[package]] name = "wit-bindgen-markdown" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "clap", @@ -2785,7 +2785,7 @@ dependencies = [ [[package]] name = "wit-bindgen-moonbit" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "clap", @@ -2796,7 +2796,7 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.40.0" +version = "0.41.0" dependencies = [ "bitflags", "futures", @@ -2805,7 +2805,7 @@ dependencies = [ [[package]] name = "wit-bindgen-rust" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "clap", @@ -2826,7 +2826,7 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-macro" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "prettyplease", @@ -2839,7 +2839,7 @@ dependencies = [ [[package]] name = "wit-bindgen-test" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index ed2b220ee..4b23406c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ resolver = "2" [workspace.package] edition = "2021" -version = "0.40.0" +version = "0.41.0" license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" repository = "https://github.com/bytecodealliance/wit-bindgen" @@ -41,14 +41,14 @@ wit-parser = "0.227.0" wit-component = "0.227.0" wasm-compose = "0.227.0" -wit-bindgen-core = { path = 'crates/core', version = '0.40.0' } -wit-bindgen-c = { path = 'crates/c', version = '0.40.0' } -wit-bindgen-rust = { path = "crates/rust", version = "0.40.0" } -wit-bindgen-csharp = { path = 'crates/csharp', version = '0.40.0' } -wit-bindgen-markdown = { path = 'crates/markdown', version = '0.40.0' } -wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.40.0' } -wit-bindgen = { path = 'crates/guest-rust', version = '0.40.0', default-features = false } -wit-bindgen-test = { path = 'crates/test', version = '0.40.0' } +wit-bindgen-core = { path = 'crates/core', version = '0.41.0' } +wit-bindgen-c = { path = 'crates/c', version = '0.41.0' } +wit-bindgen-rust = { path = "crates/rust", version = "0.41.0" } +wit-bindgen-csharp = { path = 'crates/csharp', version = '0.41.0' } +wit-bindgen-markdown = { path = 'crates/markdown', version = '0.41.0' } +wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.41.0' } +wit-bindgen = { path = 'crates/guest-rust', version = '0.41.0', default-features = false } +wit-bindgen-test = { path = 'crates/test', version = '0.41.0' } [[bin]] name = "wit-bindgen" diff --git a/crates/guest-rust/Cargo.toml b/crates/guest-rust/Cargo.toml index 0044f472a..9150b7576 100644 --- a/crates/guest-rust/Cargo.toml +++ b/crates/guest-rust/Cargo.toml @@ -12,8 +12,8 @@ Used when compiling Rust programs to the component model. """ [dependencies] -wit-bindgen-rust-macro = { path = "./macro", optional = true, version = "0.40.0" } -wit-bindgen-rt = { path = "./rt", version = "0.40.0", features = ["bitflags"] } +wit-bindgen-rust-macro = { path = "./macro", optional = true, version = "0.41.0" } +wit-bindgen-rt = { path = "./rt", version = "0.41.0", features = ["bitflags"] } [features] default = ["macros", "realloc", "async"] diff --git a/crates/guest-rust/rt/src/cabi_realloc.c b/crates/guest-rust/rt/src/cabi_realloc.c index 3855b9690..9847aef29 100644 --- a/crates/guest-rust/rt/src/cabi_realloc.c +++ b/crates/guest-rust/rt/src/cabi_realloc.c @@ -2,9 +2,9 @@ #include -extern void *cabi_realloc_wit_bindgen_0_40_0(void *ptr, size_t old_size, size_t align, size_t new_size); +extern void *cabi_realloc_wit_bindgen_0_41_0(void *ptr, size_t old_size, size_t align, size_t new_size); __attribute__((__weak__, __export_name__("cabi_realloc"))) void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { - return cabi_realloc_wit_bindgen_0_40_0(ptr, old_size, align, new_size); + return cabi_realloc_wit_bindgen_0_41_0(ptr, old_size, align, new_size); } diff --git a/crates/guest-rust/rt/src/cabi_realloc.o b/crates/guest-rust/rt/src/cabi_realloc.o index 3976b46bf95afd569348c43f3d759ad222d0d12a..3ce8afc4655bba1c34668a23282ec25d820a93dd 100644 GIT binary patch delta 11 ScmZo=YGs<>$7ndwe;WW4Cj+$r delta 11 ScmZo=YGs<>$7nFoe;WW4BLlPm diff --git a/crates/guest-rust/rt/src/cabi_realloc.rs b/crates/guest-rust/rt/src/cabi_realloc.rs index a94ffa8c7..cc738191e 100644 --- a/crates/guest-rust/rt/src/cabi_realloc.rs +++ b/crates/guest-rust/rt/src/cabi_realloc.rs @@ -1,7 +1,7 @@ // This file is generated by ./ci/rebuild-libcabi-realloc.sh #[unsafe(no_mangle)] -pub unsafe extern "C" fn cabi_realloc_wit_bindgen_0_40_0( +pub unsafe extern "C" fn cabi_realloc_wit_bindgen_0_41_0( old_ptr: *mut u8, old_len: usize, align: usize, diff --git a/crates/guest-rust/rt/src/libwit_bindgen_cabi_realloc.a b/crates/guest-rust/rt/src/libwit_bindgen_cabi_realloc.a index 8ec843fed2a08efe9748efa8d866f4a3ea9172f7..c14633576beda7faa3bcd443d3b828bcc7df32f2 100644 GIT binary patch delta 11 TcmbQkJcoJ06Gp>{PqzaA8ny)z delta 11 TcmbQkJcoJ06GnrHPqzaA8nOiu