From 092ffea4db1e8dfb48f9806446f104c686695a39 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:01:54 +0000 Subject: [PATCH 1/3] Restore support for the wasm32-wasip2 target This adjusts find_mir_or_eval_fn to respect #[wasm_import_module] on wasm. To avoid having to thread through the import module everywhere, I opted to merge it with the item name with a $$$ as separator. This is unlikely to occur in a real import module name or item name. This reverts commit c38128b0d43ae39f93bc8039b7950ab2000ec925 and makes a few minor adjustments. --- README.md | 1 + ci/ci.sh | 1 + src/machine.rs | 18 ++++- src/shims/env.rs | 2 +- src/shims/foreign_items.rs | 5 ++ src/shims/mod.rs | 1 + src/shims/tls.rs | 1 + src/shims/wasi/foreign_items.rs | 124 ++++++++++++++++++++++++++++++++ src/shims/wasi/mod.rs | 1 + 9 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 src/shims/wasi/foreign_items.rs create mode 100644 src/shims/wasi/mod.rs diff --git a/README.md b/README.md index f6c675839e..0cbfe0e96a 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,7 @@ degree documented below): - `solaris` / `illumos`: maintained by @devnexen. Supports the entire test suite. - `freebsd`: maintained by @YohDeadfall and @LorrensP-2158466. Supports the entire test suite. - `android`: **maintainer wanted**. Support very incomplete, but a basic "hello world" works. + - `wasi`: **maintainer wanted**. Support very incomplete, but a basic "hello world" works. - For targets on other operating systems, Miri might fail before even reaching the `main` function. However, even for targets that we do support, the degree of support for accessing platform APIs diff --git a/ci/ci.sh b/ci/ci.sh index 2d27f02749..bcc110f648 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -153,6 +153,7 @@ case $HOST_TARGET in BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency epoll eventfd + TEST_TARGET=wasm32-wasip2 run_tests_minimal $BASIC hello wasm TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std ;; diff --git a/src/machine.rs b/src/machine.rs index 3b18545fee..c72b725e8c 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -1243,7 +1243,23 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { // foreign function // Any needed call to `goto_block` will be performed by `emulate_foreign_item`. let args = ecx.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit? - let link_name = Symbol::intern(ecx.tcx.symbol_name(instance).name); + + let link_name = if ecx.tcx.sess.target.is_like_wasm + && let Some(module) = + ecx.tcx.wasm_import_module_map(instance.def_id().krate).get(&instance.def_id()) + { + // Adapted from https://github.com/rust-lang/rust/blob/90b65889799733f21ebdf59d96411aa531c5900a/compiler/rustc_codegen_llvm/src/attributes.rs#L549-L562 + let codegen_fn_attrs = ecx.tcx.codegen_instance_attrs(instance.def); + let name = codegen_fn_attrs + .symbol_name + .unwrap_or_else(|| ecx.tcx.item_name(instance.def_id())); + // $$$ is unlikely to occur in either the import module name or item name, so use it + // as a separator here. It will be split again in emulate_foreign_item_inner for wasi. + Symbol::intern(&format!("{}$$${}", module, name)) + } else { + Symbol::intern(ecx.tcx.symbol_name(instance).name) + }; + return ecx.emulate_foreign_item(link_name, abi, &args, dest, ret, unwind); } diff --git a/src/shims/env.rs b/src/shims/env.rs index b9fb9192df..689cd3a726 100644 --- a/src/shims/env.rs +++ b/src/shims/env.rs @@ -51,7 +51,7 @@ impl<'tcx> EnvVars<'tcx> { } else if ecx.tcx.sess.target.os == "windows" { EnvVars::Windows(WindowsEnvVars::new(ecx, env_vars)?) } else { - // For "none" targets (i.e., without an OS). + // Used e.g. for wasi EnvVars::Uninit }; ecx.machine.env_vars = env_vars; diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 54fe27382e..74a1ac729e 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -102,6 +102,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_ref(); match this.tcx.sess.target.os.as_ref() { os if this.target_os_is_unix() => shims::unix::foreign_items::is_dyn_sym(name, os), + "wasi" => shims::wasi::foreign_items::is_dyn_sym(name), "windows" => shims::windows::foreign_items::is_dyn_sym(name), _ => false, } @@ -845,6 +846,10 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_inner( this, link_name, abi, args, dest, ), + "wasi" => + shims::wasi::foreign_items::EvalContextExt::emulate_foreign_item_inner( + this, link_name, abi, args, dest, + ), "windows" => shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_inner( this, link_name, abi, args, dest, diff --git a/src/shims/mod.rs b/src/shims/mod.rs index e51ace2fd9..7f7bc3b1cf 100644 --- a/src/shims/mod.rs +++ b/src/shims/mod.rs @@ -8,6 +8,7 @@ mod math; #[cfg(all(unix, feature = "native-lib"))] mod native_lib; mod unix; +mod wasi; mod windows; mod x86; diff --git a/src/shims/tls.rs b/src/shims/tls.rs index 9dc829d7a1..1200029692 100644 --- a/src/shims/tls.rs +++ b/src/shims/tls.rs @@ -253,6 +253,7 @@ impl<'tcx> TlsDtorsState<'tcx> { } _ => { // No TLS dtor support. + // FIXME: should we do something on wasi? break 'new_state Done; } } diff --git a/src/shims/wasi/foreign_items.rs b/src/shims/wasi/foreign_items.rs new file mode 100644 index 0000000000..6fcf671ceb --- /dev/null +++ b/src/shims/wasi/foreign_items.rs @@ -0,0 +1,124 @@ +use rustc_abi::CanonAbi; +use rustc_middle::ty::Ty; +use rustc_span::Symbol; +use rustc_target::callconv::FnAbi; + +use crate::shims::alloc::EvalContextExt as _; +use crate::*; + +pub fn is_dyn_sym(_name: &str) -> bool { + false +} + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn emulate_foreign_item_inner( + &mut self, + link_name: Symbol, + abi: &FnAbi<'tcx, Ty<'tcx>>, + args: &[OpTy<'tcx>], + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx, EmulateItemResult> { + let this = self.eval_context_mut(); + + let (interface, name) = if let Some((module, name)) = link_name.as_str().split_once("$$$") { + // According to the component model, the version should be matched as semver, but for + // simplicity we strip the version entirely for now. Once we support wasm-wasip3 it may + // become actually important to match on the version, but for now it shouldn't matter. + let (module, _version) = module + .split_once('@') + .ok_or_else(|| err_unsup_format!("module name {module} must contain a version"))?; + (Some(module), name) + } else { + // This item is provided by wasi-libc, not imported from the wasi runtime + (None, link_name.as_str()) + }; + + match (interface, name) { + // Allocation + (None, "posix_memalign") => { + let [memptr, align, size] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let result = this.posix_memalign(memptr, align, size)?; + this.write_scalar(result, dest)?; + } + (None, "aligned_alloc") => { + let [align, size] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let res = this.aligned_alloc(align, size)?; + this.write_pointer(res, dest)?; + } + + // Standard input/output + // FIXME: These shims are hacks that just get basic stdout/stderr working. We can't + // constrain them to "std" since std itself uses the wasi crate for this. + (Some("wasi:cli/stdout"), "get-stdout") => { + let [] = + this.check_shim_sig(shim_sig!(extern "C" fn() -> i32), link_name, abi, args)?; + this.write_scalar(Scalar::from_i32(1), dest)?; // POSIX FD number for stdout + } + (Some("wasi:cli/stderr"), "get-stderr") => { + let [] = + this.check_shim_sig(shim_sig!(extern "C" fn() -> i32), link_name, abi, args)?; + this.write_scalar(Scalar::from_i32(2), dest)?; // POSIX FD number for stderr + } + (Some("wasi:io/streams"), "[resource-drop]output-stream") => { + let [handle] = + this.check_shim_sig(shim_sig!(extern "C" fn(i32) -> ()), link_name, abi, args)?; + let handle = this.read_scalar(handle)?.to_i32()?; + + if !(handle == 1 || handle == 2) { + throw_unsup_format!("wasm output-stream: unsupported handle"); + } + // We don't actually close these FDs, so this is a NOP. + } + (Some("wasi:io/streams"), "[method]output-stream.blocking-write-and-flush") => { + let [handle, buf, len, ret_area] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *mut _, usize, *mut _) -> ()), + link_name, + abi, + args, + )?; + let handle = this.read_scalar(handle)?.to_i32()?; + let buf = this.read_pointer(buf)?; + let len = this.read_target_usize(len)?; + let ret_area = this.read_pointer(ret_area)?; + + if len > 4096 { + throw_unsup_format!( + "wasm output-stream.blocking-write-and-flush: buffer too big" + ); + } + let len = usize::try_from(len).unwrap(); + let Some(fd) = this.machine.fds.get(handle) else { + throw_unsup_format!( + "wasm output-stream.blocking-write-and-flush: unsupported handle" + ); + }; + fd.write( + this.machine.communicate(), + buf, + len, + this, + callback!( + @capture<'tcx> { + len: usize, + ret_area: Pointer, + } + |this, result: Result| { + if !matches!(result, Ok(l) if l == len) { + throw_unsup_format!("wasm output-stream.blocking-write-and-flush: returning errors is not supported"); + } + // 0 in the first byte of the ret_area indicates success. + let ret = this.ptr_to_mplace(ret_area, this.machine.layouts.u8); + this.write_null(&ret)?; + interp_ok(()) + }), + )?; + } + + _ => return interp_ok(EmulateItemResult::NotSupported), + } + interp_ok(EmulateItemResult::NeedsReturn) + } +} diff --git a/src/shims/wasi/mod.rs b/src/shims/wasi/mod.rs new file mode 100644 index 0000000000..09c6507b24 --- /dev/null +++ b/src/shims/wasi/mod.rs @@ -0,0 +1 @@ +pub mod foreign_items; From 804a6e3a30d0db2858494ef37bfec0a45fd464dc Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:39:18 +0000 Subject: [PATCH 2/3] Move wasm_import_module_map handling into shims/wasi --- src/machine.rs | 28 ++++++++++------------------ src/shims/foreign_items.rs | 8 +++++--- src/shims/wasi/foreign_items.rs | 23 +++++++++++++++++------ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/machine.rs b/src/machine.rs index c72b725e8c..61c70dbe32 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -1243,24 +1243,16 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { // foreign function // Any needed call to `goto_block` will be performed by `emulate_foreign_item`. let args = ecx.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit? - - let link_name = if ecx.tcx.sess.target.is_like_wasm - && let Some(module) = - ecx.tcx.wasm_import_module_map(instance.def_id().krate).get(&instance.def_id()) - { - // Adapted from https://github.com/rust-lang/rust/blob/90b65889799733f21ebdf59d96411aa531c5900a/compiler/rustc_codegen_llvm/src/attributes.rs#L549-L562 - let codegen_fn_attrs = ecx.tcx.codegen_instance_attrs(instance.def); - let name = codegen_fn_attrs - .symbol_name - .unwrap_or_else(|| ecx.tcx.item_name(instance.def_id())); - // $$$ is unlikely to occur in either the import module name or item name, so use it - // as a separator here. It will be split again in emulate_foreign_item_inner for wasi. - Symbol::intern(&format!("{}$$${}", module, name)) - } else { - Symbol::intern(ecx.tcx.symbol_name(instance).name) - }; - - return ecx.emulate_foreign_item(link_name, abi, &args, dest, ret, unwind); + let link_name = Symbol::intern(ecx.tcx.symbol_name(instance).name); + return ecx.emulate_foreign_item( + Some(instance), + link_name, + abi, + &args, + dest, + ret, + unwind, + ); } if ecx.machine.data_race.as_genmc_ref().is_some() diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 74a1ac729e..23fb6ae805 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -43,6 +43,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// is delegated to another function. fn emulate_foreign_item( &mut self, + instance: Option>, link_name: Symbol, abi: &FnAbi<'tcx, Ty<'tcx>>, args: &[OpTy<'tcx>], @@ -73,7 +74,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let dest = this.force_allocation(dest)?; // The rest either implements the logic, or falls back to `lookup_exported_symbol`. - match this.emulate_foreign_item_inner(link_name, abi, args, &dest)? { + match this.emulate_foreign_item_inner(instance, link_name, abi, args, &dest)? { EmulateItemResult::NeedsReturn => { trace!("{:?}", this.dump_place(&dest.clone().into())); this.return_to_block(ret)?; @@ -118,7 +119,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ret: Option, unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { - let res = self.emulate_foreign_item(sym.0, abi, args, dest, ret, unwind)?; + let res = self.emulate_foreign_item(None, sym.0, abi, args, dest, ret, unwind)?; assert!(res.is_none(), "DynSyms that delegate are not supported"); interp_ok(()) } @@ -248,6 +249,7 @@ impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { fn emulate_foreign_item_inner( &mut self, + instance: Option>, link_name: Symbol, abi: &FnAbi<'tcx, Ty<'tcx>>, args: &[OpTy<'tcx>], @@ -848,7 +850,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { ), "wasi" => shims::wasi::foreign_items::EvalContextExt::emulate_foreign_item_inner( - this, link_name, abi, args, dest, + this, instance, link_name, abi, args, dest, ), "windows" => shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_inner( diff --git a/src/shims/wasi/foreign_items.rs b/src/shims/wasi/foreign_items.rs index 6fcf671ceb..b19e56a3dc 100644 --- a/src/shims/wasi/foreign_items.rs +++ b/src/shims/wasi/foreign_items.rs @@ -1,5 +1,5 @@ use rustc_abi::CanonAbi; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{Instance, Ty}; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -14,6 +14,7 @@ impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn emulate_foreign_item_inner( &mut self, + instance: Option>, link_name: Symbol, abi: &FnAbi<'tcx, Ty<'tcx>>, args: &[OpTy<'tcx>], @@ -21,20 +22,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); - let (interface, name) = if let Some((module, name)) = link_name.as_str().split_once("$$$") { + let (interface, name) = if let Some(instance) = instance + && let Some(module) = + this.tcx.wasm_import_module_map(instance.def_id().krate).get(&instance.def_id()) + { + // Adapted from https://github.com/rust-lang/rust/blob/90b65889799733f21ebdf59d96411aa531c5900a/compiler/rustc_codegen_llvm/src/attributes.rs#L549-L562 + let codegen_fn_attrs = this.tcx.codegen_instance_attrs(instance.def); + let name = codegen_fn_attrs + .symbol_name + .unwrap_or_else(|| this.tcx.item_name(instance.def_id())); + // According to the component model, the version should be matched as semver, but for // simplicity we strip the version entirely for now. Once we support wasm-wasip3 it may // become actually important to match on the version, but for now it shouldn't matter. - let (module, _version) = module + let (interface, _version) = module .split_once('@') .ok_or_else(|| err_unsup_format!("module name {module} must contain a version"))?; - (Some(module), name) + + (Some(interface), name) } else { // This item is provided by wasi-libc, not imported from the wasi runtime - (None, link_name.as_str()) + (None, link_name) }; - match (interface, name) { + match (interface, name.as_str()) { // Allocation (None, "posix_memalign") => { let [memptr, align, size] = From 1bb382f154e0c9692d8867c6d729956f7cebd53e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:39:56 +0000 Subject: [PATCH 3/3] Update comment --- src/shims/env.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shims/env.rs b/src/shims/env.rs index 689cd3a726..cc29a803e0 100644 --- a/src/shims/env.rs +++ b/src/shims/env.rs @@ -51,7 +51,7 @@ impl<'tcx> EnvVars<'tcx> { } else if ecx.tcx.sess.target.os == "windows" { EnvVars::Windows(WindowsEnvVars::new(ecx, env_vars)?) } else { - // Used e.g. for wasi + // For "none" targets (i.e., without an OS), and wasi. EnvVars::Uninit }; ecx.machine.env_vars = env_vars;