diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1b1e60ca51..15d2fa4c18 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -204,12 +204,16 @@ jobs: - run: (cd docs/rust_wasi_markdown_parser && cargo build) - run: (cd docs && mdbook test -L ../target/debug/deps,./rust_wasi_markdown_parser/target/debug/deps) - # Build Rust API documentation + # Build Rust API documentation. + # We pass in the `component-model` feature + # to match the docs.rs metadata in + # crates/wasmtime/Cargo.toml. - run: | cargo doc --no-deps --workspace \ --exclude wasmtime-cli \ --exclude test-programs \ - --exclude cranelift-codegen-meta + --exclude cranelift-codegen-meta \ + --features component-model - run: cargo doc --package cranelift-codegen-meta --document-private-items # Assemble the documentation, and always upload it as an artifact for diff --git a/Cargo.lock b/Cargo.lock index 36182b4548..357251d095 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3423,6 +3423,7 @@ dependencies = [ "wasmtime-jit", "wasmtime-runtime", "wasmtime-wasi", + "wasmtime-winch", "wat", "windows-sys", ] @@ -3872,6 +3873,7 @@ dependencies = [ "wasmtime-cranelift-shared", "wasmtime-environ", "winch-codegen", + "winch-environ", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6f081ca40a..047cc9a0f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ rustix = { workspace = true, features = ["mm", "param"] } [dev-dependencies] # depend again on wasmtime to activate its default features for tests -wasmtime = { workspace = true, features = ['component-model', 'async', 'default'] } +wasmtime = { workspace = true, features = ['component-model', 'async', 'default', 'winch'] } env_logger = { workspace = true } log = { workspace = true } filecheck = "0.5.0" @@ -121,6 +121,7 @@ wasmtime-cache = { path = "crates/cache", version = "=8.0.0" } wasmtime-cli-flags = { path = "crates/cli-flags", version = "=8.0.0" } wasmtime-cranelift = { path = "crates/cranelift", version = "=8.0.0" } wasmtime-cranelift-shared = { path = "crates/cranelift-shared", version = "=8.0.0" } +wasmtime-winch = { path = "crates/winch", version = "=8.0.0" } wasmtime-environ = { path = "crates/environ", version = "=8.0.0" } wasmtime-explorer = { path = "crates/explorer", version = "=8.0.0" } wasmtime-fiber = { path = "crates/fiber", version = "=8.0.0" } @@ -226,6 +227,7 @@ component-model = [ "wasmtime-wast/component-model", "wasmtime-cli-flags/component-model" ] +winch = ["wasmtime/winch"] # Stub feature that does nothing, for Cargo-features compatibility: the new # backend is the default now. diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 1bb053b95a..538f39d678 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -20,7 +20,7 @@ use anyhow::{bail, Result}; use clap::Parser; use std::collections::HashMap; use std::path::PathBuf; -use wasmtime::{Config, ProfilingStrategy}; +use wasmtime::{Config, ProfilingStrategy, Strategy}; pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ ("all", "enables all supported WebAssembly features"), @@ -240,6 +240,12 @@ pub struct CommonOptions { /// performance cost. #[clap(long)] pub relaxed_simd_deterministic: bool, + /// Explicitly specify the name of the compiler to use for WebAssembly. + /// + /// Currently only `cranelift` and `winch` are supported, but not all builds + /// of Wasmtime have both built in. + #[clap(long)] + pub compiler: Option, } impl CommonOptions { @@ -258,6 +264,13 @@ impl CommonOptions { pub fn config(&self, target: Option<&str>) -> Result { let mut config = Config::new(); + config.strategy(match self.compiler.as_deref() { + None => Strategy::Auto, + Some("cranelift") => Strategy::Cranelift, + Some("winch") => Strategy::Winch, + Some(s) => bail!("unknown compiler: {s}"), + }); + // Set the target before setting any cranelift options, since the // target will reset any target-specific options. if let Some(target) = target { diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index ad07fa396c..3ea14a0e29 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -12,6 +12,9 @@ rust-version.workspace = true [package.metadata.docs.rs] rustdoc-args = ["--cfg", "nightlydoc"] +# Docs.rs will use the `component-model` feature for documentation; +# so this feature also passed in to the `cargo doc` invocation in CI. +# See .github/workflows/main.yml features = ["component-model"] [dependencies] @@ -21,6 +24,7 @@ wasmtime-jit = { workspace = true } wasmtime-cache = { workspace = true, optional = true } wasmtime-fiber = { workspace = true, optional = true } wasmtime-cranelift = { workspace = true, optional = true } +wasmtime-winch = { workspace = true, optional = true } wasmtime-component-macro = { workspace = true, optional = true } wasmtime-component-util = { workspace = true, optional = true } target-lexicon = { workspace = true } @@ -73,6 +77,11 @@ default = [ # precompiled WebAssembly modules. cranelift = ["dep:wasmtime-cranelift"] +# Enables support for winch, the WebAssembly baseline compiler. The Winch compiler +# strategy in `Config` will be available. It is currently in active development +# and shouldn't be used in production applications. +winch = ["dep:wasmtime-winch"] + # Enables support for incremental compilation cache to be enabled in `Config`. incremental-cache = ["wasmtime-cranelift?/incremental-cache"] @@ -103,7 +112,10 @@ pooling-allocator = ["wasmtime-runtime/pooling-allocator"] # Enables support for all architectures in Cranelift, allowing # cross-compilation using the `wasmtime` crate's API, notably the # `Engine::precompile_module` function. -all-arch = ["wasmtime-cranelift?/all-arch"] +all-arch = [ + "wasmtime-cranelift?/all-arch", + "wasmtime-winch?/all-arch", +] # Enables trap handling using POSIX signals instead of Mach exceptions on MacOS. # It is useful for applications that do not bind their own exception ports and @@ -116,8 +128,10 @@ posix-signals-on-macos = ["wasmtime-runtime/posix-signals-on-macos"] component-model = [ "wasmtime-environ/component-model", "wasmtime-cranelift?/component-model", + "wasmtime-winch?/component-model", "wasmtime-runtime/component-model", "dep:wasmtime-component-macro", "dep:wasmtime-component-util", "dep:encoding_rs", ] + diff --git a/crates/wasmtime/build.rs b/crates/wasmtime/build.rs index 9555ff2475..0809a45341 100644 --- a/crates/wasmtime/build.rs +++ b/crates/wasmtime/build.rs @@ -12,7 +12,7 @@ fn main() { // frequently then all rustdoc attributes also need to be updated with the // new condition to ensure the documentation accurately reflects when an API // is available. - if cfg!(feature = "cranelift") { + if cfg!(feature = "cranelift") || cfg!(feature = "winch") { println!("cargo:rustc-cfg=compiler"); } diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index e42d405b64..8b534ede0a 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -85,7 +85,7 @@ impl Component { // // FIXME: need to write more docs here. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result { let bytes = bytes.as_ref(); #[cfg(feature = "wat")] @@ -98,7 +98,7 @@ impl Component { // // FIXME: need to write more docs here. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn from_file(engine: &Engine, file: impl AsRef) -> Result { match Self::new( engine, @@ -124,7 +124,7 @@ impl Component { // // FIXME: need to write more docs here. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result { engine .check_compatible_with_native_host() diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 0d314823dd..c214283921 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -220,7 +220,7 @@ impl Config { /// /// This method will error if the given target triple is not supported. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn target(&mut self, target: &str) -> Result<&mut Self> { self.compiler_config.target = Some(target_lexicon::Triple::from_str(target).map_err(|e| anyhow::anyhow!(e))?); @@ -820,7 +820,7 @@ impl Config { /// /// The default value for this is `Strategy::Auto`. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn strategy(&mut self, strategy: Strategy) -> &mut Self { self.compiler_config.strategy = strategy; self @@ -854,7 +854,7 @@ impl Config { /// /// The default value for this is `false` #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn cranelift_debug_verifier(&mut self, enable: bool) -> &mut Self { let val = if enable { "true" } else { "false" }; self.compiler_config @@ -871,7 +871,7 @@ impl Config { /// /// The default value for this is `OptLevel::None`. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn cranelift_opt_level(&mut self, level: OptLevel) -> &mut Self { let val = match level { OptLevel::None => "none", @@ -895,7 +895,7 @@ impl Config { /// /// The default value for this is `true`. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs #[deprecated( since = "5.0.0", note = "egraphs will be the default and this method will be removed in a future version." @@ -917,7 +917,7 @@ impl Config { /// /// The default value for this is `false` #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn cranelift_nan_canonicalization(&mut self, enable: bool) -> &mut Self { let val = if enable { "true" } else { "false" }; self.compiler_config @@ -943,7 +943,7 @@ impl Config { /// cause `Engine::new` fail if the flag's name does not exist, or the value is not appropriate /// for the flag type. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub unsafe fn cranelift_flag_enable(&mut self, flag: &str) -> &mut Self { self.compiler_config.flags.insert(flag.to_string()); self @@ -969,7 +969,7 @@ impl Config { /// For example, feature `wasm_backtrace` will set `unwind_info` to `true`, but if it's /// manually set to false then it will fail. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub unsafe fn cranelift_flag_set(&mut self, name: &str, value: &str) -> &mut Self { self.compiler_config .settings @@ -1546,7 +1546,18 @@ impl Config { #[cfg(compiler)] pub(crate) fn build_compiler(&mut self) -> Result> { let mut compiler = match self.compiler_config.strategy { - Strategy::Auto | Strategy::Cranelift => wasmtime_cranelift::builder(), + #[cfg(feature = "cranelift")] + Strategy::Auto => wasmtime_cranelift::builder(), + #[cfg(all(feature = "winch", not(feature = "cranelift")))] + Strategy::Auto => wasmtime_winch::builder(), + #[cfg(feature = "cranelift")] + Strategy::Cranelift => wasmtime_cranelift::builder(), + #[cfg(not(feature = "cranelift"))] + Strategy::Cranelift => bail!("cranelift support not compiled in"), + #[cfg(feature = "winch")] + Strategy::Winch => wasmtime_winch::builder(), + #[cfg(not(feature = "winch"))] + Strategy::Winch => bail!("winch support not compiled in"), }; if let Some(target) = &self.compiler_config.target { @@ -1711,6 +1722,10 @@ pub enum Strategy { /// Currently the default backend, Cranelift aims to be a reasonably fast /// code generator which generates high quality machine code. Cranelift, + + /// A baseline compiler for WebAssembly, currently under active development and not ready for + /// production applications. + Winch, } /// Possible optimization levels for the Cranelift codegen backend. diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index de7ba922d8..6153194186 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -218,7 +218,7 @@ impl Engine { /// [binary]: https://webassembly.github.io/spec/core/binary/index.html /// [text]: https://webassembly.github.io/spec/core/text/index.html #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn precompile_module(&self, bytes: &[u8]) -> Result> { #[cfg(feature = "wat")] let bytes = wat::parse_bytes(&bytes)?; @@ -229,7 +229,7 @@ impl Engine { /// Same as [`Engine::precompile_module`] except for a /// [`Component`](crate::component::Component) #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs #[cfg(feature = "component-model")] #[cfg_attr(nightlydoc, doc(cfg(feature = "component-model")))] pub fn precompile_component(&self, bytes: &[u8]) -> Result> { diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index ae44316bda..35f76922d1 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -346,7 +346,7 @@ impl Func { /// /// [`Trap`]: crate::Trap #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn new( store: impl AsContextMut, ty: FuncType, @@ -384,7 +384,7 @@ impl Func { /// the `func` provided correctly interprets the argument types provided to /// it, or that the results it produces will be of the correct type. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub unsafe fn new_unchecked( mut store: impl AsContextMut, ty: FuncType, diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index 6280e9c582..62821362a9 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -275,7 +275,7 @@ impl Linker { /// # } /// ``` #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn define_unknown_imports_as_traps(&mut self, module: &Module) -> anyhow::Result<()> { for import in module.imports() { if let Err(import_err) = self._get_by_import(&import) { @@ -311,7 +311,7 @@ impl Linker { /// # } /// ``` #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn define_unknown_imports_as_default_values( &mut self, module: &Module, @@ -416,7 +416,7 @@ impl Linker { /// /// For more information see [`Linker::func_wrap`]. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn func_new( &mut self, module: &str, @@ -434,7 +434,7 @@ impl Linker { /// /// For more information see [`Linker::func_wrap`]. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub unsafe fn func_new_unchecked( &mut self, module: &str, @@ -754,7 +754,7 @@ impl Linker { /// # } /// ``` #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn module( &mut self, mut store: impl AsContextMut, diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 1da66f547a..3d72c7b27a 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -196,7 +196,7 @@ impl Module { /// # } /// ``` #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result { let bytes = bytes.as_ref(); #[cfg(feature = "wat")] @@ -233,7 +233,7 @@ impl Module { /// # } /// ``` #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn from_file(engine: &Engine, file: impl AsRef) -> Result { match Self::new( engine, @@ -286,7 +286,7 @@ impl Module { /// # } /// ``` #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result { engine .check_compatible_with_native_host() @@ -358,7 +358,7 @@ impl Module { /// reflect the current state of the file, not necessarily the origianl /// state of the file. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub unsafe fn from_trusted_file(engine: &Engine, file: impl AsRef) -> Result { let mmap = MmapVec::from_file(file.as_ref())?; if &mmap[0..4] == b"\x7fELF" { @@ -738,7 +738,7 @@ impl Module { /// this method can be useful to get the serialized version without /// compiling twice. #[cfg(compiler)] - #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))] // see build.rs pub fn serialize(&self) -> Result> { // The current representation of compiled modules within a compiled // component means that it cannot be serialized. The mmap returned here diff --git a/crates/winch/Cargo.toml b/crates/winch/Cargo.toml index 8c006624ea..6c22e4e6f2 100644 --- a/crates/winch/Cargo.toml +++ b/crates/winch/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/bytecodealliance/wasmtime" [dependencies] winch-codegen = { workspace = true } +winch-environ = { workspace = true } target-lexicon = { workspace = true } wasmtime-environ = { workspace = true } anyhow = { workspace = true } @@ -19,6 +20,5 @@ wasmparser = { workspace = true } gimli = { workspace = true } [features] -default = ["component-model"] component-model = ["wasmtime-environ/component-model"] all-arch = ["winch-codegen/all-arch"] diff --git a/crates/winch/src/compiler.rs b/crates/winch/src/compiler.rs index 3a0f55bd75..6d18f7194b 100644 --- a/crates/winch/src/compiler.rs +++ b/crates/winch/src/compiler.rs @@ -1,50 +1,136 @@ use anyhow::Result; +use cranelift_codegen::{Final, MachBufferFinalized}; use object::write::{Object, SymbolId}; use std::any::Any; +use std::sync::Mutex; +use wasmparser::FuncValidatorAllocations; +use wasmtime_cranelift_shared::obj::ModuleTextBuilder; use wasmtime_environ::{ - CompileError, DefinedFuncIndex, FuncIndex, FunctionBodyData, FunctionLoc, ModuleTranslation, - ModuleTypes, PrimaryMap, Tunables, WasmFunctionInfo, + CompileError, DefinedFuncIndex, FilePos, FuncIndex, FunctionBodyData, FunctionLoc, + ModuleTranslation, ModuleTypes, PrimaryMap, Tunables, WasmFunctionInfo, }; use winch_codegen::TargetIsa; +use winch_environ::FuncEnv; pub(crate) struct Compiler { isa: Box, + allocations: Mutex>, } +struct CompiledFunction(MachBufferFinalized); + impl Compiler { pub fn new(isa: Box) -> Self { - Self { isa } + Self { + isa, + allocations: Mutex::new(Vec::new()), + } + } + + fn take_allocations(&self) -> FuncValidatorAllocations { + self.allocations + .lock() + .unwrap() + .pop() + .unwrap_or_else(Default::default) + } + + fn save_allocations(&self, allocs: FuncValidatorAllocations) { + self.allocations.lock().unwrap().push(allocs) } } impl wasmtime_environ::Compiler for Compiler { fn compile_function( &self, - _translation: &ModuleTranslation<'_>, - _index: DefinedFuncIndex, - _data: FunctionBodyData<'_>, + translation: &ModuleTranslation<'_>, + index: DefinedFuncIndex, + data: FunctionBodyData<'_>, _tunables: &Tunables, _types: &ModuleTypes, ) -> Result<(WasmFunctionInfo, Box), CompileError> { - todo!() + let index = translation.module.func_index(index); + let sig = translation.get_types().function_at(index.as_u32()).unwrap(); + let FunctionBodyData { body, validator } = data; + let start_srcloc = FilePos::new( + body.get_binary_reader() + .original_position() + .try_into() + .unwrap(), + ); + let mut validator = validator.into_validator(self.take_allocations()); + let env = FuncEnv::new(&translation.module, translation.get_types(), &self.isa); + let buffer = self + .isa + .compile_function(&sig, &body, &env, &mut validator) + .map_err(|e| CompileError::Codegen(format!("{e:?}"))); + self.save_allocations(validator.into_allocations()); + let buffer = buffer?; + + Ok(( + WasmFunctionInfo { + start_srcloc, + stack_maps: Box::new([]), + }, + Box::new(CompiledFunction(buffer)), + )) } fn compile_host_to_wasm_trampoline( &self, - _ty: &wasmtime_environ::WasmFuncType, + ty: &wasmtime_environ::WasmFuncType, ) -> Result, CompileError> { - todo!() + let wasm_ty = wasmparser::FuncType::new( + ty.params().iter().copied().map(Into::into), + ty.returns().iter().copied().map(Into::into), + ); + + let buffer = self + .isa + .host_to_wasm_trampoline(&wasm_ty) + .map_err(|e| CompileError::Codegen(format!("{:?}", e)))?; + + Ok(Box::new(CompiledFunction(buffer))) } fn append_code( &self, - _obj: &mut Object<'static>, - _funcs: &[(String, Box)], + obj: &mut Object<'static>, + funcs: &[(String, Box)], _tunables: &Tunables, - _resolve_reloc: &dyn Fn(usize, FuncIndex) -> usize, + resolve_reloc: &dyn Fn(usize, FuncIndex) -> usize, ) -> Result> { - assert!(_funcs.is_empty()); - Ok(Vec::new()) + let mut builder = + ModuleTextBuilder::new(obj, self, self.isa.text_section_builder(funcs.len())); + + let mut ret = Vec::with_capacity(funcs.len()); + for (i, (sym, func)) in funcs.iter().enumerate() { + let func = &func.downcast_ref::().unwrap().0; + + // TODO: Implement copying over this data into the + // `ModuleTextBuilder` type. Note that this should probably be + // deduplicated with the cranelift implementation in the long run. + assert!(func.relocs().is_empty()); + assert!(func.traps().is_empty()); + assert!(func.stack_maps().is_empty()); + + let (sym, range) = builder.append_func( + &sym, + func.data(), + self.function_alignment(), + None, + &[], + |idx| resolve_reloc(i, idx), + ); + + let info = FunctionLoc { + start: u32::try_from(range.start).unwrap(), + length: u32::try_from(range.end - range.start).unwrap(), + }; + ret.push((sym, info)); + } + builder.finish(); + Ok(ret) } fn emit_trampoline_obj( diff --git a/scripts/publish.rs b/scripts/publish.rs index b64b178db8..3ff02789ec 100644 --- a/scripts/publish.rs +++ b/scripts/publish.rs @@ -56,8 +56,8 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wasmtime-cranelift", "wasmtime-jit", "wasmtime-cache", - "wasmtime-winch", "winch-environ", + "wasmtime-winch", "wasmtime", // wasi-common/wiggle "wiggle", diff --git a/tests/all/main.rs b/tests/all/main.rs index 80455d383f..bb86700062 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -34,6 +34,9 @@ mod traps; mod wait_notify; mod wasi_testsuite; mod wast; +// Currently Winch is only supported in x86_64 unix platforms. +#[cfg(all(target_arch = "x86_64", target_family = "unix"))] +mod winch; /// A helper to compile a module in a new store with reference types enabled. pub(crate) fn ref_types_module( diff --git a/tests/all/winch.rs b/tests/all/winch.rs new file mode 100644 index 0000000000..289a69260f --- /dev/null +++ b/tests/all/winch.rs @@ -0,0 +1,100 @@ +use anyhow::Result; +use wasmtime::*; + +#[test] +fn compiles_with_winch() -> Result<()> { + let mut c = Config::new(); + + c.strategy(Strategy::Winch); + + let engine = Engine::new(&c)?; + + // Winch only supports a very basic function signature for now while it's being developed. + let test_mod = r#" + (module + (func $test (result i32) + (i32.const 42) + ) + (export "test" (func $test)) + ) + "#; + + let mut store = Store::new(&engine, ()); + + let module = Module::new(&engine, test_mod)?; + + let instance = Instance::new(&mut store, &module, &[])?; + + let f = instance + .get_func(&mut store, "test") + .ok_or(anyhow::anyhow!("test function not found"))?; + + let mut returns = vec![Val::null(); 1]; + + // Winch doesn't support calling typed functions at the moment. + f.call(&mut store, &[], &mut returns)?; + + assert_eq!(returns.len(), 1); + assert_eq!(returns[0].unwrap_i32(), 42); + + Ok(()) +} + +#[test] +fn compiles_with_winch_stack_arguments() -> Result<()> { + let mut c = Config::new(); + + c.strategy(Strategy::Winch); + + let engine = Engine::new(&c)?; + + // Winch only supports a very basic function signature for now while it's being developed. + let test_mod = r#" + (module + (func $sum10 (param $arg_1 i32) (param $arg_2 i32) (param $arg_3 i32) (param $arg_4 i32) (param $arg_5 i32) (param $arg_6 i32) (param $arg_7 i32) (param $arg_8 i32) (param $arg_9 i32) (param $arg_10 i32) (result i32) + local.get $arg_1 + local.get $arg_2 + i32.add + local.get $arg_3 + i32.add + local.get $arg_4 + i32.add + local.get $arg_5 + i32.add + local.get $arg_6 + i32.add + local.get $arg_7 + i32.add + local.get $arg_8 + i32.add + local.get $arg_9 + i32.add + local.get $arg_10 + i32.add) + (export "sum10" (func $sum10)) + ) + "#; + + let mut store = Store::new(&engine, ()); + + let module = Module::new(&engine, test_mod)?; + + let instance = Instance::new(&mut store, &module, &[])?; + + let f = instance + .get_func(&mut store, "sum10") + .ok_or(anyhow::anyhow!("sum10 function not found"))?; + + let mut returns = vec![Val::null(); 1]; + + // create a new Val array with ten 1s + let args = vec![Val::I32(1); 10]; + + // Winch doesn't support calling typed functions at the moment. + f.call(&mut store, &args, &mut returns)?; + + assert_eq!(returns.len(), 1); + assert_eq!(returns[0].unwrap_i32(), 10); + + Ok(()) +} diff --git a/winch/codegen/src/abi/local.rs b/winch/codegen/src/abi/local.rs index a951dac446..0a58b92a07 100644 --- a/winch/codegen/src/abi/local.rs +++ b/winch/codegen/src/abi/local.rs @@ -4,7 +4,7 @@ use wasmparser::ValType; /// Slots for stack arguments are addressed from the frame pointer. /// Slots for function-defined locals and for registers are addressed /// from the stack pointer. -#[derive(Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] enum Base { FP, SP, @@ -14,6 +14,7 @@ enum Base { /// /// Represents the type, location and addressing mode of a local /// in the stack's local and argument area. +#[derive(Clone)] pub(crate) struct LocalSlot { /// The offset of the local slot. pub offset: u32, diff --git a/winch/codegen/src/abi/mod.rs b/winch/codegen/src/abi/mod.rs index 83a39cf6db..2425b9990f 100644 --- a/winch/codegen/src/abi/mod.rs +++ b/winch/codegen/src/abi/mod.rs @@ -210,3 +210,10 @@ where let alignment_mask = alignment - 1.into(); (value + alignment_mask) & !alignment_mask } + +/// Calculates the delta needed to adjust a function's frame plus some +/// addend to a given alignment. +pub(crate) fn calculate_frame_adjustment(frame_size: u32, addend: u32, alignment: u32) -> u32 { + let total = frame_size + addend; + (alignment - (total % alignment)) % alignment +} diff --git a/winch/codegen/src/codegen/call.rs b/winch/codegen/src/codegen/call.rs index c2907ab24b..89e0d69492 100644 --- a/winch/codegen/src/codegen/call.rs +++ b/winch/codegen/src/codegen/call.rs @@ -2,8 +2,8 @@ //! calling convention, see [ABI]. use super::CodeGenContext; use crate::{ - abi::{align_to, ABIArg, ABIResult, ABISig, ABI}, - masm::{MacroAssembler, OperandSize}, + abi::{align_to, calculate_frame_adjustment, ABIArg, ABIResult, ABISig, ABI}, + masm::{CalleeKind, MacroAssembler, OperandSize}, reg::Reg, stack::Val, }; @@ -39,7 +39,7 @@ pub(crate) struct FnCall<'a> { /// │ │ /// │ Stack space created by any previous spills │ /// │ from the value stack; and which memory values │ - /// │ are used as function arguments. │ + /// │ are used as function arguments. │ /// │ │ /// ├──────────────────────────────────────────────────┤ ---> The Wasm value stack at this point in time would look like: /// │ │ [ Reg | Reg | Mem(offset) | Mem(offset) ] @@ -51,7 +51,7 @@ pub(crate) struct FnCall<'a> { /// ├─────────────────────────────────────────────────┬┤ ---> The Wasm value stack at this point in time would look like: /// │ │ [ Mem(offset) | Mem(offset) | Mem(offset) | Mem(offset) ] /// │ │ Assuming that the callee takes 4 arguments, we calculate - /// │ │ 2 spilled registers + 2 memory values; all of which will be used + /// │ │ 2 spilled registers + 2 memory values; all of which will be used /// │ Stack space allocated for │ as arguments to the call via `assign_args`, thus the memory they represent is /// │ the callee function arguments in the stack; │ is considered to be consumed by the call. /// │ represented by `arg_stack_space` │ @@ -59,7 +59,7 @@ pub(crate) struct FnCall<'a> { /// │ │ /// │ │ /// └──────────────────────────────────────────────────┘ ------> Stack pointer when emitting the call - /// + /// total_stack_space: u32, /// The total stack space needed for the callee arguments on the /// stack, including any adjustments to the function's frame and @@ -161,7 +161,7 @@ impl<'a> FnCall<'a> { ) { masm.reserve_stack(self.arg_stack_space); self.assign_args(context, masm, ::scratch_reg()); - masm.call(callee); + masm.call(CalleeKind::Direct(callee)); masm.free_stack(self.total_stack_space); context.drop_last(self.abi_sig.params.len()); // The stack pointer at the end of the function call @@ -213,10 +213,3 @@ impl<'a> FnCall<'a> { } } } - -/// Calculates the delta needed to adjust a function's frame plus some -/// addend to a given alignment. -fn calculate_frame_adjustment(frame_size: u32, addend: u32, alignment: u32) -> u32 { - let total = frame_size + addend; - (alignment - (total % alignment)) % alignment -} diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs index 7ffbaf5e9c..115a37117c 100644 --- a/winch/codegen/src/codegen/mod.rs +++ b/winch/codegen/src/codegen/mod.rs @@ -10,7 +10,7 @@ mod context; pub(crate) use context::*; mod env; pub use env::*; -mod call; +pub mod call; /// The code generation abstraction. pub(crate) struct CodeGen<'a, A, M> @@ -59,7 +59,7 @@ where pub fn emit( &mut self, body: &mut BinaryReader<'a>, - validator: FuncValidator, + validator: &mut FuncValidator, ) -> Result<()> { self.emit_start() .and_then(|_| self.emit_body(body, validator)) @@ -78,7 +78,7 @@ where fn emit_body( &mut self, body: &mut BinaryReader<'a>, - mut validator: FuncValidator, + validator: &mut FuncValidator, ) -> Result<()> { self.spill_register_arguments(); let defined_locals_range = &self.context.frame.defined_locals_range; diff --git a/winch/codegen/src/frame/mod.rs b/winch/codegen/src/frame/mod.rs index 519917fe76..5e59bf68cf 100644 --- a/winch/codegen/src/frame/mod.rs +++ b/winch/codegen/src/frame/mod.rs @@ -20,6 +20,47 @@ impl DefinedLocalsRange { } } +/// An abstraction to read the defined locals from the WASM binary for a function. +#[derive(Default)] +pub(crate) struct DefinedLocals { + /// The defined locals for a function. + pub defined_locals: Locals, + /// The size of the defined locals. + pub stack_size: u32, +} + +impl DefinedLocals { + /// Compute the local slots for a WASM function. + pub fn new( + reader: &mut BinaryReader<'_>, + validator: &mut FuncValidator, + ) -> Result { + let mut next_stack = 0; + // The first 32 bits of a WASM binary function describe the number of locals + let local_count = reader.read_var_u32()?; + let mut slots: Locals = Default::default(); + + for _ in 0..local_count { + let position = reader.original_position(); + let count = reader.read_var_u32()?; + let ty = reader.read()?; + validator.define_locals(position, count, ty)?; + + let ty: ValType = ty.try_into()?; + for _ in 0..count { + let ty_size = ty_size(&ty); + next_stack = align_to(next_stack, ty_size) + ty_size; + slots.push(LocalSlot::new(ty, next_stack)); + } + } + + Ok(Self { + defined_locals: slots, + stack_size: next_stack, + }) + } +} + /// Frame handler abstraction. pub(crate) struct Frame { /// The size of the entire local area; the arguments plus the function defined locals. @@ -37,22 +78,29 @@ pub(crate) struct Frame { impl Frame { /// Allocate a new Frame. - pub fn new( - sig: &ABISig, - body: &mut BinaryReader<'_>, - validator: &mut FuncValidator, - abi: &A, - ) -> Result { + pub fn new(sig: &ABISig, defined_locals: &DefinedLocals, abi: &A) -> Result { let (mut locals, defined_locals_start) = Self::compute_arg_slots(sig, abi)?; - let (defined_slots, defined_locals_end) = - Self::compute_defined_slots(body, validator, defined_locals_start)?; - locals.extend(defined_slots); - let locals_size = align_to(defined_locals_end, abi.stack_align().into()); + + // The defined locals have a zero-based offset by default + // so we need to add the defined locals start to the offset. + locals.extend( + defined_locals + .defined_locals + .iter() + .map(|l| LocalSlot::new(l.ty, l.offset + defined_locals_start)), + ); + + let locals_size = align_to( + defined_locals_start + defined_locals.stack_size, + abi.stack_align().into(), + ); Ok(Self { locals, locals_size, - defined_locals_range: DefinedLocalsRange(defined_locals_start..defined_locals_end), + defined_locals_range: DefinedLocalsRange( + defined_locals_start..defined_locals.stack_size, + ), }) } @@ -115,30 +163,4 @@ impl Frame { ABIArg::Stack { ty, offset } => LocalSlot::stack_arg(*ty, offset + arg_base_offset), } } - - fn compute_defined_slots( - reader: &mut BinaryReader<'_>, - validator: &mut FuncValidator, - next_stack: u32, - ) -> Result<(Locals, u32)> { - let mut next_stack = next_stack; - let local_count = reader.read_var_u32()?; - let mut slots: Locals = Default::default(); - - for _ in 0..local_count { - let position = reader.original_position(); - let count = reader.read_var_u32()?; - let ty = reader.read()?; - validator.define_locals(position, count, ty)?; - - let ty: ValType = ty.try_into()?; - for _ in 0..count { - let ty_size = ty_size(&ty); - next_stack = align_to(next_stack, ty_size) + ty_size; - slots.push(LocalSlot::new(ty, next_stack)); - } - } - - Ok((slots, next_stack)) - } } diff --git a/winch/codegen/src/isa/aarch64/masm.rs b/winch/codegen/src/isa/aarch64/masm.rs index 92b30218f8..d28c1bb267 100644 --- a/winch/codegen/src/isa/aarch64/masm.rs +++ b/winch/codegen/src/isa/aarch64/masm.rs @@ -7,7 +7,7 @@ use crate::{ abi::local::LocalSlot, codegen::CodeGenContext, isa::reg::Reg, - masm::{DivKind, MacroAssembler as Masm, OperandSize, RegImm, RemKind}, + masm::{CalleeKind, DivKind, MacroAssembler as Masm, OperandSize, RegImm, RemKind}, }; use cranelift_codegen::{settings, Final, MachBufferFinalized}; @@ -136,7 +136,7 @@ impl Masm for MacroAssembler { self.asm.str(src, dst, size); } - fn call(&mut self, _callee: u32) { + fn call(&mut self, _callee: CalleeKind) { todo!() } @@ -148,7 +148,7 @@ impl Masm for MacroAssembler { todo!() } - fn sp_offset(&mut self) -> u32 { + fn sp_offset(&self) -> u32 { self.sp_offset } @@ -194,6 +194,10 @@ impl Masm for MacroAssembler { self.sp_offset } + + fn address_from_reg(&self, reg: Reg, offset: u32) -> Self::Address { + Address::offset(reg, offset as i64) + } } impl MacroAssembler { diff --git a/winch/codegen/src/isa/aarch64/mod.rs b/winch/codegen/src/isa/aarch64/mod.rs index 6934d04924..6aa5ea9ac0 100644 --- a/winch/codegen/src/isa/aarch64/mod.rs +++ b/winch/codegen/src/isa/aarch64/mod.rs @@ -2,7 +2,7 @@ use self::regs::{scratch, ALL_GPR}; use crate::{ abi::ABI, codegen::{CodeGen, CodeGenContext}, - frame::Frame, + frame::{DefinedLocals, Frame}, isa::{Builder, TargetIsa}, masm::MacroAssembler, regalloc::RegAlloc, @@ -86,14 +86,16 @@ impl TargetIsa for Aarch64 { sig: &FuncType, body: &FunctionBody, env: &dyn FuncEnv, - mut validator: FuncValidator, + validator: &mut FuncValidator, ) -> Result> { let mut body = body.get_binary_reader(); let mut masm = Aarch64Masm::new(self.shared_flags.clone()); let stack = Stack::new(); let abi = abi::Aarch64ABI::default(); let abi_sig = abi.sig(sig); - let frame = Frame::new(&abi_sig, &mut body, &mut validator, &abi)?; + + let defined_locals = DefinedLocals::new(&mut body, validator)?; + let frame = Frame::new(&abi_sig, &defined_locals, &abi)?; // TODO: Add floating point bitmask let regalloc = RegAlloc::new(RegSet::new(ALL_GPR, 0), scratch()); let codegen_context = CodeGenContext::new(regalloc, stack, &frame); @@ -113,4 +115,8 @@ impl TargetIsa for Aarch64 { // See `cranelift_codegen::isa::TargetIsa::function_alignment`. 32 } + + fn host_to_wasm_trampoline(&self, _ty: &FuncType) -> Result> { + todo!() + } } diff --git a/winch/codegen/src/isa/mod.rs b/winch/codegen/src/isa/mod.rs index 058c9c70e4..0f1689864b 100644 --- a/winch/codegen/src/isa/mod.rs +++ b/winch/codegen/src/isa/mod.rs @@ -90,12 +90,13 @@ pub trait TargetIsa: Send + Sync { false } + /// Compile a function. fn compile_function( &self, sig: &FuncType, body: &FunctionBody, env: &dyn FuncEnv, - validator: FuncValidator, + validator: &mut FuncValidator, ) -> Result>; /// Get the default calling convention of the underlying target triple. @@ -119,6 +120,9 @@ pub trait TargetIsa: Send + Sync { /// See `cranelift_codegen::isa::TargetIsa::function_alignment`. fn function_alignment(&self) -> u32; + + /// Generate a trampoline that can be used to call a wasm function from wasmtime. + fn host_to_wasm_trampoline(&self, ty: &FuncType) -> Result>; } impl Debug for &dyn TargetIsa { diff --git a/winch/codegen/src/isa/x64/asm.rs b/winch/codegen/src/isa/x64/asm.rs index 8f03489e20..d582f16ba8 100644 --- a/winch/codegen/src/isa/x64/asm.rs +++ b/winch/codegen/src/isa/x64/asm.rs @@ -2,7 +2,7 @@ use crate::{ isa::reg::Reg, - masm::{DivKind, OperandSize, RemKind}, + masm::{CalleeKind, DivKind, OperandSize, RemKind}, }; use cranelift_codegen::{ entity::EntityRef, @@ -469,17 +469,31 @@ impl Assembler { }); } - /// Direct function call to a user defined function. - pub fn call(&mut self, callee: u32) { - let dest = ExternalName::user(UserExternalNameRef::new(callee as usize)); - self.emit(Inst::CallKnown { - dest, - info: Box::new(CallInfo { - uses: smallvec![], - defs: smallvec![], - clobbers: Default::default(), - opcode: Opcode::Call, - }), - }); + pub fn call(&mut self, callee: CalleeKind) { + match callee { + CalleeKind::Indirect(reg) => { + self.emit(Inst::CallUnknown { + dest: RegMem::reg(reg.into()), + info: Box::new(CallInfo { + uses: smallvec![], + defs: smallvec![], + clobbers: Default::default(), + opcode: Opcode::Call, + }), + }); + } + CalleeKind::Direct(index) => { + let dest = ExternalName::user(UserExternalNameRef::new(index as usize)); + self.emit(Inst::CallKnown { + dest, + info: Box::new(CallInfo { + uses: smallvec![], + defs: smallvec![], + clobbers: Default::default(), + opcode: Opcode::Call, + }), + }); + } + } } } diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs index cc973b24af..0dab09a202 100644 --- a/winch/codegen/src/isa/x64/masm.rs +++ b/winch/codegen/src/isa/x64/masm.rs @@ -3,9 +3,9 @@ use super::{ asm::{Assembler, Operand}, regs::{self, rbp, rsp}, }; -use crate::isa::reg::Reg; use crate::masm::{DivKind, MacroAssembler as Masm, OperandSize, RegImm, RemKind}; use crate::{abi::LocalSlot, codegen::CodeGenContext, stack::Val}; +use crate::{isa::reg::Reg, masm::CalleeKind}; use cranelift_codegen::{isa::x64::settings as x64_settings, settings, Final, MachBufferFinalized}; /// x64 MacroAssembler. @@ -114,7 +114,7 @@ impl Masm for MacroAssembler { self.decrement_sp(8); } - fn call(&mut self, callee: u32) { + fn call(&mut self, callee: CalleeKind) { self.asm.call(callee); } @@ -124,7 +124,7 @@ impl Masm for MacroAssembler { self.asm.mov(src, dst, size); } - fn sp_offset(&mut self) -> u32 { + fn sp_offset(&self) -> u32 { self.sp_offset } @@ -236,6 +236,10 @@ impl Masm for MacroAssembler { fn finalize(self) -> MachBufferFinalized { self.asm.finalize() } + + fn address_from_reg(&self, reg: Reg, offset: u32) -> Self::Address { + Address::offset(reg, offset) + } } impl MacroAssembler { diff --git a/winch/codegen/src/isa/x64/mod.rs b/winch/codegen/src/isa/x64/mod.rs index 78d85a38c7..a34eb46367 100644 --- a/winch/codegen/src/isa/x64/mod.rs +++ b/winch/codegen/src/isa/x64/mod.rs @@ -1,10 +1,14 @@ -use crate::abi::ABI; -use crate::codegen::{CodeGen, CodeGenContext}; -use crate::frame::Frame; +use crate::{ + abi::ABI, + codegen::{CodeGen, CodeGenContext}, +}; + +use crate::frame::{DefinedLocals, Frame}; use crate::isa::x64::masm::MacroAssembler as X64Masm; use crate::masm::MacroAssembler; use crate::regalloc::RegAlloc; use crate::stack::Stack; +use crate::trampoline::Trampoline; use crate::FuncEnv; use crate::{ isa::{Builder, TargetIsa}, @@ -87,14 +91,16 @@ impl TargetIsa for X64 { sig: &FuncType, body: &FunctionBody, env: &dyn FuncEnv, - mut validator: FuncValidator, + validator: &mut FuncValidator, ) -> Result> { let mut body = body.get_binary_reader(); let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone()); let stack = Stack::new(); let abi = abi::X64ABI::default(); let abi_sig = abi.sig(sig); - let frame = Frame::new(&abi_sig, &mut body, &mut validator, &abi)?; + + let defined_locals = DefinedLocals::new(&mut body, validator)?; + let frame = Frame::new(&abi_sig, &defined_locals, &abi)?; // TODO Add in floating point bitmask let regalloc = RegAlloc::new(RegSet::new(ALL_GPR, 0), regs::scratch()); let codegen_context = CodeGenContext::new(regalloc, stack, &frame); @@ -113,4 +119,15 @@ impl TargetIsa for X64 { // See `cranelift_codegen`'s value of this for more information. 16 } + + fn host_to_wasm_trampoline(&self, ty: &FuncType) -> Result> { + let abi = abi::X64ABI::default(); + let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone()); + + let mut trampoline = Trampoline::new(&mut masm, &abi, regs::scratch(), regs::argv()); + + trampoline.emit_host_to_wasm(ty); + + Ok(masm.finalize()) + } } diff --git a/winch/codegen/src/isa/x64/regs.rs b/winch/codegen/src/isa/x64/regs.rs index 18710024ff..0c30b59c35 100644 --- a/winch/codegen/src/isa/x64/regs.rs +++ b/winch/codegen/src/isa/x64/regs.rs @@ -81,6 +81,18 @@ pub(crate) fn scratch() -> Reg { r11() } +/// This register is used as a scratch register, in the context of trampolines only, +/// where we assume that callee-saved registers are given the correct handling +/// according to the system ABI. r12 is chosen given that it's a callee-saved, +/// non-argument register. +/// +/// In the context of all other internal functions, this register is not excluded +/// from register allocation, so no extra assumptions should be made regarding +/// its availability. +pub(crate) fn argv() -> Reg { + r12() +} + fn fpr(enc: u8) -> Reg { Reg::new(PReg::new(enc as usize, RegClass::Float)) } diff --git a/winch/codegen/src/lib.rs b/winch/codegen/src/lib.rs index 3321998db7..f01bd63a40 100644 --- a/winch/codegen/src/lib.rs +++ b/winch/codegen/src/lib.rs @@ -16,4 +16,5 @@ mod masm; mod regalloc; mod regset; mod stack; +mod trampoline; mod visitor; diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs index 38ce406557..31e9684788 100644 --- a/winch/codegen/src/masm.rs +++ b/winch/codegen/src/masm.rs @@ -39,6 +39,13 @@ pub(crate) enum RegImm { Imm(i64), } +pub(crate) enum CalleeKind { + /// A function call to a raw address. + Indirect(Reg), + /// A function call to a local function. + Direct(u32), +} + impl RegImm { /// Register constructor. pub fn reg(r: Reg) -> Self { @@ -102,11 +109,14 @@ pub(crate) trait MacroAssembler { /// current position of the stack pointer (e.g. [sp + offset]. fn address_at_sp(&self, offset: u32) -> Self::Address; - /// Emit a function call to a locally defined function. - fn call(&mut self, callee: u32); + /// Construct an address that is relative to the given register. + fn address_from_reg(&self, reg: Reg, offset: u32) -> Self::Address; + + /// Emit a function call to either a local or external function. + fn call(&mut self, callee: CalleeKind); /// Get stack pointer offset. - fn sp_offset(&mut self) -> u32; + fn sp_offset(&self) -> u32; /// Perform a stack store. fn store(&mut self, src: RegImm, dst: Self::Address, size: OperandSize); diff --git a/winch/codegen/src/trampoline.rs b/winch/codegen/src/trampoline.rs new file mode 100644 index 0000000000..d970855c2e --- /dev/null +++ b/winch/codegen/src/trampoline.rs @@ -0,0 +1,176 @@ +use crate::{ + abi::{align_to, calculate_frame_adjustment, ABIArg, ABIResult, ABI}, + masm::{CalleeKind, MacroAssembler, OperandSize, RegImm}, + reg::Reg, +}; +use std::mem; +use wasmparser::{FuncType, ValType}; + +/// A trampoline to provide interopt between different calling conventions. +pub(crate) struct Trampoline<'a, A, M> +where + A: ABI, + M: MacroAssembler, +{ + /// The macro assembler. + masm: &'a mut M, + /// The ABI. + abi: &'a A, + /// The main scratch register for the current architecture. It is not allocatable for the callee. + scratch_reg: Reg, + /// A second scratch register. This will be allocatable for the callee, so it can only be used + /// after the callee-saved registers are on the stack. + alloc_scratch_reg: Reg, +} + +impl<'a, A, M> Trampoline<'a, A, M> +where + A: ABI, + M: MacroAssembler, +{ + /// Create a new trampoline. + pub fn new(masm: &'a mut M, abi: &'a A, scratch_reg: Reg, alloc_scratch_reg: Reg) -> Self { + Self { + masm, + abi, + scratch_reg, + alloc_scratch_reg, + } + } + + /// Emit the host to wasm trampoline. + pub fn emit_host_to_wasm(&mut self, ty: &FuncType) { + // The host to wasm trampoline is currently hard coded (see vmcontext.rs in the + // wasmtime-runtime crate, VMTrampoline). + // The first two parameters are VMContexts (not used at this time). + // The third parameter is the function pointer to call. + // The fourth parameter is an address to storage space for both the return value and the + // arguments to the function. + let trampoline_ty = FuncType::new( + vec![ValType::I64, ValType::I64, ValType::I64, ValType::I64], + vec![], + ); + + // TODO: We should be passing a calling convention here so the signature can determine the + // correct location of arguments. When we fully support system ABIs, this will need to be + // updated. + let trampoline_sig = self.abi.sig(&trampoline_ty); + + // Hard-coding the size in bytes of the trampoline arguments since it's static, based on + // the current signature we should always have 4 arguments, each of which is 8 bytes. + let trampoline_arg_size = 32; + + let callee_sig = self.abi.sig(ty); + + let val_ptr = if let ABIArg::Reg { reg, ty: _ty } = &trampoline_sig.params[3] { + Ok(RegImm::reg(*reg)) + } else { + Err(anyhow::anyhow!("Expected the val ptr to be in a register")) + } + .unwrap(); + + self.masm.prologue(); + + // TODO: When we include support for passing calling conventions, we need to update this to + // adhere to the system ABI. Currently, callee-saved registers are not preserved while we + // are building this out. + + let mut trampoline_arg_offsets: [u32; 4] = [0; 4]; + + trampoline_sig + .params + .iter() + .enumerate() + .for_each(|(i, param)| { + if let ABIArg::Reg { reg, ty: _ty } = param { + let offset = self.masm.push(*reg); + trampoline_arg_offsets[i] = offset; + } + }); + + let val_ptr_offset = trampoline_arg_offsets[3]; + let func_ptr_offset = trampoline_arg_offsets[2]; + + self.masm.mov( + val_ptr, + RegImm::reg(self.scratch_reg), + crate::masm::OperandSize::S64, + ); + + // How much we need to adjust the stack pointer by to account for the alignment + // required by the ISA. + let delta = calculate_frame_adjustment( + self.masm.sp_offset(), + self.abi.arg_base_offset() as u32, + self.abi.call_stack_align() as u32, + ); + + // The total amount of stack space we need to reserve for the arguments. + let total_arg_stack_space = align_to( + callee_sig.stack_bytes + delta, + self.abi.call_stack_align() as u32, + ); + + self.masm.reserve_stack(total_arg_stack_space); + + // The max size a value can be when reading from the params memory location. + let value_size = mem::size_of::(); + + callee_sig.params.iter().enumerate().for_each(|(i, param)| { + let value_offset = (i * value_size) as u32; + + match param { + ABIArg::Reg { reg, ty } => self.masm.load( + self.masm.address_from_reg(self.scratch_reg, value_offset), + *reg, + (*ty).into(), + ), + ABIArg::Stack { offset, ty } => { + self.masm.load( + self.masm.address_from_reg(self.scratch_reg, value_offset), + self.alloc_scratch_reg, + (*ty).into(), + ); + self.masm.store( + RegImm::reg(self.alloc_scratch_reg), + self.masm.address_at_sp(*offset), + (*ty).into(), + ); + } + } + }); + + // Move the function pointer from it's stack location into a scratch register. + self.masm.load( + self.masm.address_from_sp(func_ptr_offset), + self.scratch_reg, + OperandSize::S64, + ); + + // Call the function that was passed into the trampoline. + self.masm.call(CalleeKind::Indirect(self.scratch_reg)); + + self.masm.free_stack(total_arg_stack_space); + + // Move the val ptr back into the scratch register so we can load the return values. + self.masm.load( + self.masm.address_from_sp(val_ptr_offset), + self.scratch_reg, + OperandSize::S64, + ); + + // Move the return values into the value ptr. + // We are only support a single return value at this time. + let ABIResult::Reg { reg, ty } = &callee_sig.result; + self.masm.store( + RegImm::reg(*reg), + self.masm.address_from_reg(self.scratch_reg, 0), + (*ty).unwrap().into(), + ); + + // TODO: Once we support system ABIs better, callee-saved registers will need to be + // restored here. + + self.masm.epilogue(trampoline_arg_size); + } +} diff --git a/winch/environ/src/lib.rs b/winch/environ/src/lib.rs index 1e46ea6ac8..66f9e4c471 100644 --- a/winch/environ/src/lib.rs +++ b/winch/environ/src/lib.rs @@ -15,7 +15,7 @@ pub struct FuncEnv<'a> { /// Type information about a module, once it has been validated. pub types: &'a Types, /// The current ISA. - pub isa: &'a dyn TargetIsa, + pub isa: &'a Box, } impl<'a> winch_codegen::FuncEnv for FuncEnv<'a> { @@ -35,7 +35,7 @@ impl<'a> winch_codegen::FuncEnv for FuncEnv<'a> { impl<'a> FuncEnv<'a> { /// Create a new function environment. - pub fn new(module: &'a Module, types: &'a Types, isa: &'a dyn TargetIsa) -> Self { + pub fn new(module: &'a Module, types: &'a Types, isa: &'a Box) -> Self { Self { module, types, isa } } } diff --git a/winch/filetests/src/disasm.rs b/winch/filetests/src/disasm.rs index 8b816430ec..482a86a4a7 100644 --- a/winch/filetests/src/disasm.rs +++ b/winch/filetests/src/disasm.rs @@ -7,7 +7,7 @@ use target_lexicon::Architecture; use winch_codegen::TargetIsa; /// Disassemble and print a machine code buffer. -pub fn disasm(bytes: &[u8], isa: &dyn TargetIsa) -> Result> { +pub fn disasm(bytes: &[u8], isa: &Box) -> Result> { let dis = disassembler_for(isa)?; let insts = dis.disasm_all(bytes, 0x0).unwrap(); @@ -44,7 +44,7 @@ pub fn disasm(bytes: &[u8], isa: &dyn TargetIsa) -> Result> { Ok(disassembled_lines) } -fn disassembler_for(isa: &dyn TargetIsa) -> Result { +fn disassembler_for(isa: &Box) -> Result { let disasm = match isa.triple().architecture { Architecture::X86_64 => Capstone::new() .x86() diff --git a/winch/filetests/src/lib.rs b/winch/filetests/src/lib.rs index a6d744717c..0765404eb4 100644 --- a/winch/filetests/src/lib.rs +++ b/winch/filetests/src/lib.rs @@ -109,7 +109,7 @@ mod test { let body_inputs = std::mem::take(&mut translation.function_body_inputs); let module = &translation.module; let types = translation.get_types(); - let env = FuncEnv::new(module, &types, &*isa); + let env = FuncEnv::new(module, &types, &isa); let binding = body_inputs .into_iter() @@ -151,11 +151,11 @@ mod test { .function_at(index.as_u32()) .expect(&format!("function type at index {:?}", index.as_u32())); let FunctionBodyData { body, validator } = f.1; - let validator = validator.into_validator(Default::default()); + let mut validator = validator.into_validator(Default::default()); let buffer = env .isa - .compile_function(&sig, &body, env, validator) + .compile_function(&sig, &body, env, &mut validator) .expect("Couldn't compile function"); disasm(buffer.data(), env.isa).unwrap() diff --git a/winch/src/compile.rs b/winch/src/compile.rs index c08fa800b8..8ad774a058 100644 --- a/winch/src/compile.rs +++ b/winch/src/compile.rs @@ -41,7 +41,7 @@ pub fn run(opt: &Options) -> Result<()> { let body_inputs = std::mem::take(&mut translation.function_body_inputs); let module = &translation.module; let types = translation.get_types(); - let env = FuncEnv::new(module, &types, &*isa); + let env = FuncEnv::new(module, &types, &isa); body_inputs .into_iter() @@ -57,10 +57,10 @@ fn compile(env: &FuncEnv, f: (DefinedFuncIndex, FunctionBodyData<'_>)) -> Result .function_at(index.as_u32()) .expect(&format!("function type at index {:?}", index.as_u32())); let FunctionBodyData { body, validator } = f.1; - let validator = validator.into_validator(Default::default()); + let mut validator = validator.into_validator(Default::default()); let buffer = env .isa - .compile_function(&sig, &body, env, validator) + .compile_function(&sig, &body, env, &mut validator) .expect("Couldn't compile function"); println!("Disassembly for function: {}", index.as_u32()); @@ -68,5 +68,15 @@ fn compile(env: &FuncEnv, f: (DefinedFuncIndex, FunctionBodyData<'_>)) -> Result .iter() .for_each(|s| println!("{}", s)); + let buffer = env + .isa + .host_to_wasm_trampoline(sig) + .expect("Couldn't compile trampoline"); + + println!("Disassembly for trampoline: {}", index.as_u32()); + disasm(buffer.data(), env.isa)? + .iter() + .for_each(|s| println!("{}", s)); + Ok(()) }