diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bc13378761..3a84d26e79 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -172,6 +172,12 @@ jobs: - run: cargo fetch --locked - run: cargo fetch --locked --manifest-path crates/test-programs/wasi-tests/Cargo.toml + # Build some various feature combinations + - run: cargo build --manifest-path crates/api/Cargo.toml --no-default-features + - run: cargo build --manifest-path crates/api/Cargo.toml --features wat + - run: cargo build --manifest-path crates/api/Cargo.toml --features lightbeam + if: matrix.rust == 'nightly' + # Build and test all features except for lightbeam - run: cargo test --features test_programs --all --exclude lightbeam -- --nocapture env: diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index 4dbea09abc..b48b8a59e3 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -21,6 +21,7 @@ cfg-if = "0.1.9" backtrace = "0.3.42" rustc-demangle = "0.1.16" lazy_static = "1.4" +wat = { version = "1.0.7", optional = true } [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.3.7" @@ -37,6 +38,8 @@ wat = "1.0" maintenance = { status = "actively-developed" } [features] +default = ['wat'] + # Enables experimental support for the lightbeam codegen backend, an alternative # to cranelift. Requires Nightly Rust currently, and this is not enabled by # default. diff --git a/crates/api/examples/gcd.rs b/crates/api/examples/gcd.rs index 43a29fcf01..5f68b87417 100644 --- a/crates/api/examples/gcd.rs +++ b/crates/api/examples/gcd.rs @@ -36,9 +36,8 @@ const WAT: &str = r#" fn main() -> anyhow::Result<()> { // Load our WebAssembly (parsed WAT in our case), and then load it into a // `Module` which is attached to a `Store` cache. - let wasm = wat::parse_str(WAT)?; let store = Store::default(); - let module = Module::new(&store, &wasm)?; + let module = Module::new(&store, WAT)?; // Find index of the `gcd` export. let gcd_index = module diff --git a/crates/api/examples/hello.rs b/crates/api/examples/hello.rs index c33b2c4495..317e0361f3 100644 --- a/crates/api/examples/hello.rs +++ b/crates/api/examples/hello.rs @@ -21,21 +21,15 @@ fn main() -> Result<()> { println!("Initializing..."); let store = Store::default(); - // Next upload the `*.wasm` binary file, which in this case we're going to - // be parsing an inline text format into a binary. - println!("Loading binary..."); - let binary = wat::parse_str( - r#" - (module - (func $hello (import "" "hello")) - (func (export "run") (call $hello)) - ) - "#, - )?; - - // Compiler the `*.wasm` binary into an in-memory instance of a `Module`. + // Compile the wasm binary into an in-memory instance of a `Module`. println!("Compiling module..."); - let module = Module::new(&store, &binary).context("> Error compiling module!")?; + let wat = r#" + (module + (func $hello (import "" "hello")) + (func (export "run") (call $hello)) + ) + "#; + let module = Module::new(&store, wat).context("> Error compiling module!")?; // Here we handle the imports of the module, which in this case is our // `HelloCallback` type and its associated implementation of `Callback. diff --git a/crates/api/examples/memory.rs b/crates/api/examples/memory.rs index 15fb127280..dd2908c005 100644 --- a/crates/api/examples/memory.rs +++ b/crates/api/examples/memory.rs @@ -66,27 +66,25 @@ fn main() -> Result<(), Error> { // Load binary. println!("Loading binary..."); - let binary = wat::parse_str( - r#" - (module - (memory (export "memory") 2 3) + let wat = r#" + (module + (memory (export "memory") 2 3) - (func (export "size") (result i32) (memory.size)) - (func (export "load") (param i32) (result i32) - (i32.load8_s (local.get 0)) - ) - (func (export "store") (param i32 i32) - (i32.store8 (local.get 0) (local.get 1)) - ) + (func (export "size") (result i32) (memory.size)) + (func (export "load") (param i32) (result i32) + (i32.load8_s (local.get 0)) + ) + (func (export "store") (param i32 i32) + (i32.store8 (local.get 0) (local.get 1)) + ) - (data (i32.const 0x1000) "\01\02\03\04") - ) - "#, - )?; + (data (i32.const 0x1000) "\01\02\03\04") + ) + "#; // Compile. println!("Compiling module..."); - let module = Module::new(&store, &binary).context("> Error compiling module!")?; + let module = Module::new(&store, &wat).context("> Error compiling module!")?; // Instantiate. println!("Instantiating module..."); diff --git a/crates/api/examples/multi.rs b/crates/api/examples/multi.rs index 830d899138..663281eb09 100644 --- a/crates/api/examples/multi.rs +++ b/crates/api/examples/multi.rs @@ -48,13 +48,9 @@ fn main() -> Result<()> { let engine = Engine::new(Config::new().wasm_multi_value(true)); let store = Store::new(&engine); - // Load binary. - println!("Loading binary..."); - let binary = wat::parse_str(WAT)?; - // Compile. println!("Compiling module..."); - let module = Module::new(&store, &binary).context("Error compiling module!")?; + let module = Module::new(&store, WAT).context("Error compiling module!")?; // Create external print functions. println!("Creating callback..."); diff --git a/crates/api/src/callable.rs b/crates/api/src/callable.rs index 5b77ec00a4..1fbaa3789b 100644 --- a/crates/api/src/callable.rs +++ b/crates/api/src/callable.rs @@ -30,7 +30,7 @@ use wasmtime_runtime::Export; /// # fn main () -> Result<(), Box> { /// // Simple module that imports our host function ("times_two") and re-exports /// // it as "run". -/// let binary = wat::parse_str(r#" +/// let wat = r#" /// (module /// (func $times_two (import "" "times_two") (param i32) (result i32)) /// (func @@ -40,11 +40,11 @@ use wasmtime_runtime::Export; /// (local.get 0) /// (call $times_two)) /// ) -/// "#)?; +/// "#; /// /// // Initialise environment and our module. /// let store = wasmtime::Store::default(); -/// let module = wasmtime::Module::new(&store, &binary)?; +/// let module = wasmtime::Module::new(&store, wat)?; /// /// // Define the type of the function we're going to call. /// let times_two_type = wasmtime::FuncType::new( diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index 4ab11f529b..6aa4b10a12 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -7,6 +7,7 @@ use anyhow::{Error, Result}; use lazy_static::lazy_static; use std::cell::Cell; use std::collections::HashMap; +use std::path::Path; use std::rc::Rc; use std::sync::{Arc, RwLock}; use wasmparser::{ @@ -107,15 +108,22 @@ lazy_static! { } impl Module { - /// Creates a new WebAssembly `Module` from the given in-memory `binary` - /// data. + /// Creates a new WebAssembly `Module` from the given in-memory `bytes`. /// - /// The `binary` data provided must be a [binary-encoded][binary] - /// WebAssembly module. This means that the data for the wasm module must be - /// loaded in-memory if it's present elsewhere, for example on disk. - /// Additionally this requires that the entire binary is loaded into memory - /// all at once, this API does not support streaming compilation of a - /// module. + /// The `bytes` provided must be in one of two formats: + /// + /// * It can be a [binary-encoded][binary] WebAssembly module. This + /// is always supported. + /// * It may also be a [text-encoded][text] instance of the WebAssembly + /// text format. This is only supported when the `wat` feature of this + /// crate is enabled. If this is supplied then the text format will be + /// parsed before validation. Note that the `wat` feature is enabled by + /// default. + /// + /// The data for the wasm module must be loaded in-memory if it's present + /// elsewhere, for example on disk. This requires that the entire binary is + /// loaded into memory all at once, this API does not support streaming + /// compilation of a module. /// /// The WebAssembly binary will be decoded and validated. It will also be /// compiled according to the configuration of the provided `store` and @@ -137,34 +145,69 @@ impl Module { /// example too many locals) /// * The wasm binary may use features that are not enabled in the /// configuration of `store` + /// * If the `wat` feature is enabled and the input is text, then it may be + /// rejected if it fails to parse. /// /// The error returned should contain full information about why module /// creation failed if one is returned. /// /// [binary]: https://webassembly.github.io/spec/core/binary/index.html - pub fn new(store: &Store, binary: &[u8]) -> Result { - Module::validate(store, binary)?; - unsafe { Module::new_unchecked(store, binary) } + /// [text]: https://webassembly.github.io/spec/core/text/index.html + pub fn new(store: &Store, bytes: impl AsRef<[u8]>) -> Result { + #[cfg(feature = "wat")] + let bytes = wat::parse_bytes(bytes.as_ref())?; + Module::from_binary(store, bytes.as_ref()) } /// Creates a new WebAssembly `Module` from the given in-memory `binary` /// data. The provided `name` will be used in traps/backtrace details. /// /// See [`Module::new`] for other details. - pub fn new_with_name(store: &Store, binary: &[u8], name: &str) -> Result { - let mut module = Module::new(store, binary)?; + pub fn new_with_name(store: &Store, bytes: impl AsRef<[u8]>, name: &str) -> Result { + let mut module = Module::new(store, bytes.as_ref())?; let inner = Rc::get_mut(&mut module.inner).unwrap(); Arc::get_mut(&mut inner.names).unwrap().module_name = Some(name.to_string()); Ok(module) } + /// Creates a new WebAssembly `Module` from the contents of the given + /// `file` on disk. + /// + /// This is a convenience function that will read the `file` provided and + /// pass the bytes to the [`Module::new`] function. For more information + /// see [`Module::new`] + pub fn from_file(store: &Store, file: impl AsRef) -> Result { + #[cfg(feature = "wat")] + let wasm = wat::parse_file(file)?; + #[cfg(not(feature = "wat"))] + let wasm = std::fs::read(file)?; + Module::new(store, &wasm) + } + + /// Creates a new WebAssembly `Module` from the given in-memory `binary` + /// data. + /// + /// This is similar to [`Module::new`] except that it requires that the + /// `binary` input is a WebAssembly binary, the text format is not supported + /// by this function. It's generally recommended to use [`Module::new`], + /// but if it's required to not support the text format this function can be + /// used instead. + pub fn from_binary(store: &Store, binary: &[u8]) -> Result { + Module::validate(store, binary)?; + // Note that the call to `validate` here should be ok because we + // previously validated the binary, meaning we're guaranteed to pass a + // valid binary for `store`. + unsafe { Module::from_binary_unchecked(store, binary) } + } + /// Creates a new WebAssembly `Module` from the given in-memory `binary` /// data, skipping validation and asserting that `binary` is a valid /// WebAssembly module. /// /// This function is the same as [`Module::new`] except that it skips the - /// call to [`Module::validate`]. This means that the WebAssembly binary is - /// not validated for correctness and it is simply assumed as valid. + /// call to [`Module::validate`] and it does not support the text format of + /// WebAssembly. The WebAssembly binary is not validated for + /// correctness and it is simply assumed as valid. /// /// For more information about creation of a module and the `store` argument /// see the documentation of [`Module::new`]. @@ -184,7 +227,7 @@ impl Module { /// While this assumes that the binary is valid it still needs to actually /// be somewhat valid for decoding purposes, and the basics of decoding can /// still fail. - pub unsafe fn new_unchecked(store: &Store, binary: &[u8]) -> Result { + pub unsafe fn from_binary_unchecked(store: &Store, binary: &[u8]) -> Result { let mut ret = Module::compile(store, binary)?; ret.read_imports_and_exports(binary)?; Ok(ret) @@ -194,10 +237,11 @@ impl Module { /// configuration in `store`. /// /// This function will perform a speedy validation of the `binary` input - /// WebAssembly module (which is in [binary form][binary]) and return either - /// `Ok` or `Err` depending on the results of validation. The `store` - /// argument indicates configuration for WebAssembly features, for example, - /// which are used to indicate what should be valid and what shouldn't be. + /// WebAssembly module (which is in [binary form][binary], the text format + /// is not accepted by this function) and return either `Ok` or `Err` + /// depending on the results of validation. The `store` argument indicates + /// configuration for WebAssembly features, for example, which are used to + /// indicate what should be valid and what shouldn't be. /// /// Validation automatically happens as part of [`Module::new`], but is a /// requirement for [`Module::new_unchecked`] to be safe. diff --git a/crates/api/tests/import-indexes.rs b/crates/api/tests/import-indexes.rs index 2e41ff09eb..e1e0d41238 100644 --- a/crates/api/tests/import-indexes.rs +++ b/crates/api/tests/import-indexes.rs @@ -38,8 +38,7 @@ fn same_import_names_still_distinct() -> anyhow::Result<()> { } let store = Store::default(); - let wasm = wat::parse_str(WAT)?; - let module = Module::new(&store, &wasm)?; + let module = Module::new(&store, WAT)?; let imports = [ Func::new( diff --git a/crates/api/tests/import_calling_export.rs b/crates/api/tests/import_calling_export.rs index aad031d173..b13f868dbc 100644 --- a/crates/api/tests/import_calling_export.rs +++ b/crates/api/tests/import_calling_export.rs @@ -32,8 +32,7 @@ fn test_import_calling_export() { } let store = Store::default(); - let wasm = wat::parse_str(WAT).unwrap(); - let module = Module::new(&store, &wasm).expect("failed to create module"); + let module = Module::new(&store, WAT).expect("failed to create module"); let callback = Rc::new(Callback { other: RefCell::new(None), diff --git a/crates/api/tests/invoke_func_via_table.rs b/crates/api/tests/invoke_func_via_table.rs index 1c236e05a6..28ba779ec9 100644 --- a/crates/api/tests/invoke_func_via_table.rs +++ b/crates/api/tests/invoke_func_via_table.rs @@ -5,17 +5,15 @@ use wasmtime::*; fn test_invoke_func_via_table() -> Result<()> { let store = Store::default(); - let binary = wat::parse_str( - r#" - (module - (func $f (result i64) (i64.const 42)) + let wat = r#" + (module + (func $f (result i64) (i64.const 42)) - (table (export "table") 1 1 anyfunc) - (elem (i32.const 0) $f) - ) - "#, - )?; - let module = Module::new(&store, &binary).context("> Error compiling module!")?; + (table (export "table") 1 1 anyfunc) + (elem (i32.const 0) $f) + ) + "#; + let module = Module::new(&store, wat).context("> Error compiling module!")?; let instance = Instance::new(&module, &[]).context("> Error instantiating module!")?; let f = instance diff --git a/crates/api/tests/name.rs b/crates/api/tests/name.rs index 24c86caeea..d6cea5a50c 100644 --- a/crates/api/tests/name.rs +++ b/crates/api/tests/name.rs @@ -3,15 +3,13 @@ use wasmtime::*; #[test] fn test_module_no_name() -> anyhow::Result<()> { let store = Store::default(); - let binary = wat::parse_str( - r#" - (module - (func (export "run") (nop)) - ) - "#, - )?; + let wat = r#" + (module + (func (export "run") (nop)) + ) + "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, wat)?; assert_eq!(module.name(), None); Ok(()) @@ -20,18 +18,16 @@ fn test_module_no_name() -> anyhow::Result<()> { #[test] fn test_module_name() -> anyhow::Result<()> { let store = Store::default(); - let binary = wat::parse_str( - r#" - (module $from_name_section - (func (export "run") (nop)) - ) - "#, - )?; + let wat = r#" + (module $from_name_section + (func (export "run") (nop)) + ) + "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, wat)?; assert_eq!(module.name(), Some("from_name_section")); - let module = Module::new_with_name(&store, &binary, "override")?; + let module = Module::new_with_name(&store, wat, "override")?; assert_eq!(module.name(), Some("override")); Ok(()) diff --git a/crates/api/tests/traps.rs b/crates/api/tests/traps.rs index 159aebce5f..9c89e1aa51 100644 --- a/crates/api/tests/traps.rs +++ b/crates/api/tests/traps.rs @@ -13,16 +13,14 @@ fn test_trap_return() -> Result<()> { } let store = Store::default(); - let binary = wat::parse_str( - r#" - (module - (func $hello (import "" "hello")) - (func (export "run") (call $hello)) - ) - "#, - )?; + let wat = r#" + (module + (func $hello (import "" "hello")) + (func (export "run") (call $hello)) + ) + "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, wat)?; let hello_type = FuncType::new(Box::new([]), Box::new([])); let hello_func = Func::new(&store, hello_type, Rc::new(HelloCallback)); @@ -41,16 +39,14 @@ fn test_trap_return() -> Result<()> { #[test] fn test_trap_trace() -> Result<()> { let store = Store::default(); - let binary = wat::parse_str( - r#" - (module $hello_mod - (func (export "run") (call $hello)) - (func $hello (unreachable)) - ) - "#, - )?; + let wat = r#" + (module $hello_mod + (func (export "run") (call $hello)) + (func $hello (unreachable)) + ) + "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, wat)?; let instance = Instance::new(&module, &[])?; let run_func = instance.exports()[0] .func() @@ -82,20 +78,18 @@ fn test_trap_trace_cb() -> Result<()> { } let store = Store::default(); - let binary = wat::parse_str( - r#" - (module $hello_mod - (import "" "throw" (func $throw)) - (func (export "run") (call $hello)) - (func $hello (call $throw)) - ) - "#, - )?; + let wat = r#" + (module $hello_mod + (import "" "throw" (func $throw)) + (func (export "run") (call $hello)) + (func $hello (call $throw)) + ) + "#; let fn_type = FuncType::new(Box::new([]), Box::new([])); let fn_func = Func::new(&store, fn_type, Rc::new(ThrowCallback)); - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, wat)?; let instance = Instance::new(&module, &[fn_func.into()])?; let run_func = instance.exports()[0] .func() @@ -117,15 +111,13 @@ fn test_trap_trace_cb() -> Result<()> { #[test] fn test_trap_stack_overflow() -> Result<()> { let store = Store::default(); - let binary = wat::parse_str( - r#" - (module $rec_mod - (func $run (export "run") (call $run)) - ) - "#, - )?; + let wat = r#" + (module $rec_mod + (func $run (export "run") (call $run)) + ) + "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, wat)?; let instance = Instance::new(&module, &[])?; let run_func = instance.exports()[0] .func() @@ -148,18 +140,16 @@ fn test_trap_stack_overflow() -> Result<()> { #[test] fn trap_display_pretty() -> Result<()> { let store = Store::default(); - let binary = wat::parse_str( - r#" - (module $m - (func $die unreachable) - (func call $die) - (func $foo call 1) - (func (export "bar") call $foo) - ) - "#, - )?; + let wat = r#" + (module $m + (func $die unreachable) + (func call $die) + (func $foo call 1) + (func (export "bar") call $foo) + ) + "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, wat)?; let instance = Instance::new(&module, &[])?; let run_func = instance.exports()[0] .func() @@ -183,31 +173,27 @@ wasm backtrace: #[test] fn trap_display_multi_module() -> Result<()> { let store = Store::default(); - let binary = wat::parse_str( - r#" - (module $a - (func $die unreachable) - (func call $die) - (func $foo call 1) - (func (export "bar") call $foo) - ) - "#, - )?; + let wat = r#" + (module $a + (func $die unreachable) + (func call $die) + (func $foo call 1) + (func (export "bar") call $foo) + ) + "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, wat)?; let instance = Instance::new(&module, &[])?; let bar = instance.exports()[0].clone(); - let binary = wat::parse_str( - r#" - (module $b - (import "" "" (func $bar)) - (func $middle call $bar) - (func (export "bar2") call $middle) - ) - "#, - )?; - let module = Module::new(&store, &binary)?; + let wat = r#" + (module $b + (import "" "" (func $bar)) + (func $middle call $bar) + (func (export "bar2") call $middle) + ) + "#; + let module = Module::new(&store, wat)?; let instance = Instance::new(&module, &[bar])?; let bar2 = instance.exports()[0] .func() diff --git a/crates/c-api/src/lib.rs b/crates/c-api/src/lib.rs index d55300d8c2..2ffdf03cc1 100644 --- a/crates/c-api/src/lib.rs +++ b/crates/c-api/src/lib.rs @@ -787,7 +787,10 @@ pub unsafe extern "C" fn wasm_module_new( ) -> *mut wasm_module_t { let binary = (*binary).as_slice(); let store = &(*store).store.borrow(); - let module = Module::new_unchecked(store, binary).expect("module"); + let module = match Module::from_binary_unchecked(store, binary) { + Ok(module) => module, + Err(_) => return ptr::null_mut(), + }; let imports = module .imports() .iter() diff --git a/tests/custom_signal_handler.rs b/tests/custom_signal_handler.rs index 378b77441e..b21054b08d 100644 --- a/tests/custom_signal_handler.rs +++ b/tests/custom_signal_handler.rs @@ -107,8 +107,7 @@ mod tests { fn test_custom_signal_handler_single_instance() -> Result<()> { let engine = Engine::new(&Config::default()); let store = Store::new(&engine); - let data = wat::parse_str(WAT1)?; - let module = Module::new(&store, &data)?; + let module = Module::new(&store, WAT1)?; let instance = Instance::new(&module, &[])?; let (base, length) = set_up_memory(&instance); @@ -166,8 +165,7 @@ mod tests { fn test_custom_signal_handler_multiple_instances() -> Result<()> { let engine = Engine::new(&Config::default()); let store = Store::new(&engine); - let data = wat::parse_str(WAT1)?; - let module = Module::new(&store, &data)?; + let module = Module::new(&store, WAT1)?; // Set up multiple instances @@ -261,8 +259,7 @@ mod tests { let store = Store::new(&engine); // instance1 which defines 'read' - let data1 = wat::parse_str(WAT1)?; - let module1 = Module::new(&store, &data1)?; + let module1 = Module::new(&store, WAT1)?; let instance1 = Instance::new(&module1, &[])?; let (base1, length1) = set_up_memory(&instance1); unsafe { @@ -277,8 +274,7 @@ mod tests { let instance1_read = instance1_exports[0].clone(); // instance2 wich calls 'instance1.read' - let data2 = wat::parse_str(WAT2)?; - let module2 = Module::new(&store, &data2)?; + let module2 = Module::new(&store, WAT2)?; let instance2 = Instance::new(&module2, &[instance1_read])?; // since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle // SIGSEGV originating from within the memory of instance1