diff --git a/Cargo.lock b/Cargo.lock index fa36376f0d..dcc7920647 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2361,12 +2361,14 @@ version = "0.19.0" dependencies = [ "anyhow", "backtrace", + "bincode", "cfg-if", "lazy_static", "libc", "log", "region", "rustc-demangle", + "serde", "smallvec", "target-lexicon", "tempfile", @@ -2529,6 +2531,7 @@ dependencies = [ "more-asserts", "object 0.20.0", "region", + "serde", "target-lexicon", "thiserror", "wasmparser 0.59.0", diff --git a/cranelift/codegen/src/settings.rs b/cranelift/codegen/src/settings.rs index 4879d5c2a8..6f25b134af 100644 --- a/cranelift/codegen/src/settings.rs +++ b/cranelift/codegen/src/settings.rs @@ -45,7 +45,7 @@ pub trait Configurable { } /// Collect settings values based on a template. -#[derive(Clone)] +#[derive(Clone, Hash)] pub struct Builder { template: &'static detail::Template, bytes: Box<[u8]>, @@ -212,8 +212,10 @@ impl<'a> PredicateView<'a> { pub mod detail { use crate::constant_hash; use core::fmt; + use core::hash::Hash; /// An instruction group template. + #[derive(Hash)] pub struct Template { /// Name of the instruction group. pub name: &'static str, @@ -281,6 +283,7 @@ pub mod detail { /// A setting descriptor holds the information needed to generically set and print a setting. /// /// Each settings group will be represented as a constant DESCRIPTORS array. + #[derive(Hash)] pub struct Descriptor { /// Lower snake-case name of setting as defined in meta. pub name: &'static str, @@ -293,7 +296,7 @@ pub mod detail { } /// The different kind of settings along with descriptor bits that depend on the kind. - #[derive(Clone, Copy)] + #[derive(Clone, Copy, Hash)] pub enum Detail { /// A boolean setting only uses one bit, numbered from LSB. Bool { diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h index ee2c2fc002..75880ce020 100644 --- a/crates/c-api/include/wasmtime.h +++ b/crates/c-api/include/wasmtime.h @@ -930,6 +930,37 @@ WASM_API_EXTERN void wasmtime_externref_new_with_finalizer( */ WASM_API_EXTERN bool wasmtime_externref_data(wasm_val_t* val, void** datap); +/** + * \brief This function serializes compiled module artifacts + * as blob data. + * + * \param module the module + * \param ret if the conversion is successful, this byte vector is filled in with + * the serialized compiled module. + * + * \return a non-null error if parsing fails, or returns `NULL`. If parsing + * fails then `ret` isn't touched. + * + * This function does not take ownership of `module`, and the caller is + * expected to deallocate the returned #wasmtime_error_t and #wasm_byte_vec_t. + */ +WASM_API_EXTERN own wasmtime_error_t* wasmtime_module_serialize( + wasm_module_t* module, + own wasm_byte_vec_t *ret +); + +/** + * \brief Build a module from serialized data. + * * + * This function does not take ownership of any of its arguments, but the + * returned error and module are owned by the caller. + */ +WASM_API_EXTERN own wasmtime_error_t *wasmtime_module_deserialize( + wasm_engine_t *engine, + const wasm_byte_vec_t *serialized, + own wasm_module_t **ret +); + #undef own #ifdef __cplusplus diff --git a/crates/c-api/src/module.rs b/crates/c-api/src/module.rs index 7d2f0ff7c4..f0c8ec5147 100644 --- a/crates/c-api/src/module.rs +++ b/crates/c-api/src/module.rs @@ -130,3 +130,63 @@ pub extern "C" fn wasm_module_obtain( exports, })) } + +#[no_mangle] +pub extern "C" fn wasm_module_serialize(module: &wasm_module_t, ret: &mut wasm_byte_vec_t) { + drop(wasmtime_module_serialize(module, ret)); +} + +#[no_mangle] +pub extern "C" fn wasm_module_deserialize( + store: &wasm_store_t, + binary: &wasm_byte_vec_t, +) -> Option> { + let mut ret = ptr::null_mut(); + let engine = wasm_engine_t { + engine: store.store.engine().clone(), + }; + match wasmtime_module_deserialize(&engine, binary, &mut ret) { + Some(_err) => None, + None => { + assert!(!ret.is_null()); + Some(unsafe { Box::from_raw(ret) }) + } + } +} + +#[no_mangle] +pub extern "C" fn wasmtime_module_serialize( + module: &wasm_module_t, + ret: &mut wasm_byte_vec_t, +) -> Option> { + handle_result(module.module.serialize(), |buf| { + ret.set_buffer(buf); + }) +} + +#[no_mangle] +pub extern "C" fn wasmtime_module_deserialize( + engine: &wasm_engine_t, + binary: &wasm_byte_vec_t, + ret: &mut *mut wasm_module_t, +) -> Option> { + handle_result( + Module::deserialize(&engine.engine, binary.as_slice()), + |module| { + let imports = module + .imports() + .map(|i| wasm_importtype_t::new(i.module().to_owned(), i.name().to_owned(), i.ty())) + .collect::>(); + let exports = module + .exports() + .map(|e| wasm_exporttype_t::new(e.name().to_owned(), e.ty())) + .collect::>(); + let module = Box::new(wasm_module_t { + module: module, + imports, + exports, + }); + *ret = Box::into_raw(module); + }, + ) +} diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 2f9348fe40..07444bf94c 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -137,10 +137,10 @@ impl TablePlan { /// A translated WebAssembly module, excluding the function bodies and /// memory initializers. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Module { /// A unique identifier (within this process) for this module. - #[serde(skip_serializing, default = "Module::next_id")] + #[serde(skip_serializing, skip_deserializing, default = "Module::next_id")] pub id: usize, /// The name of this wasm module, often found in the wasm file. @@ -181,7 +181,7 @@ pub struct Module { /// This is stored within a `Module` and it implements `Hash`, unlike `Module`, /// and is used as part of the cache key when we load compiled modules from the /// global cache. -#[derive(Debug, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Hash, Serialize, Deserialize)] pub struct ModuleLocal { /// Unprocessed signatures exactly as provided by `declare_signature()`. pub signatures: PrimaryMap, diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index 1c363dcff4..9499250a12 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -9,6 +9,7 @@ use cranelift_wasm::{ Memory, MemoryIndex, ModuleTranslationState, SignatureIndex, Table, TableIndex, TargetEnvironment, WasmError, WasmFuncType, WasmResult, }; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::sync::Arc; @@ -450,7 +451,7 @@ pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir /// A memory index and offset within that memory where a data initialization /// should is to be performed. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct DataInitializerLocation { /// The index of the memory to initialize. pub memory_index: MemoryIndex, diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index 6274a05e07..3ba139d6b1 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -32,6 +32,7 @@ cfg-if = "0.1.9" log = "0.4" gimli = { version = "0.21.0", default-features = false, features = ["write"] } object = { version = "0.20", default-features = false, features = ["write"] } +serde = { version = "1.0.94", features = ["derive"] } [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.8", features = ["winnt", "impl-default"] } diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index f7c9997598..0e50a927d6 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -10,6 +10,7 @@ use crate::link::link_module; use crate::object::ObjectUnwindInfo; use crate::resolver::Resolver; use object::File as ObjectFile; +use serde::{Deserialize, Serialize}; use std::any::Any; use std::collections::HashMap; use std::sync::Arc; @@ -51,27 +52,47 @@ pub enum SetupError { DebugInfo(#[from] anyhow::Error), } -// Contains all compilation artifacts. -struct CompilationArtifacts { +/// Contains all compilation artifacts. +#[derive(Serialize, Deserialize)] +pub struct CompilationArtifacts { + /// Module metadata. module: Module, + + /// ELF image with functions code. obj: Box<[u8]>, + + /// Unwind information for function code. unwind_info: Box<[ObjectUnwindInfo]>, + + /// Data initiailizers. data_initializers: Box<[OwnedDataInitializer]>, + + /// Traps descriptors. traps: Traps, + + /// Stack map descriptors. stack_maps: StackMaps, + + /// Wasm to function code address map. address_transform: ModuleAddressMap, + + /// Debug info presence flags. + debug_info: bool, } impl CompilationArtifacts { - fn new(compiler: &Compiler, data: &[u8]) -> Result { + /// Builds compilation artifacts. + pub fn build(compiler: &Compiler, data: &[u8]) -> Result { let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables()); let translation = environ .translate(data) .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; + let debug_info = compiler.tunables().debug_info; + let mut debug_data = None; - if compiler.tunables().debug_info { + if debug_info { // TODO Do we want to ignore invalid DWARF data? debug_data = Some(read_debuginfo(&data)?); } @@ -110,6 +131,7 @@ impl CompilationArtifacts { traps, stack_maps, address_transform, + debug_info, }) } } @@ -136,6 +158,8 @@ pub struct CompiledModule { traps: Traps, stack_maps: StackMaps, address_transform: ModuleAddressMap, + obj: Box<[u8]>, + unwind_info: Box<[ObjectUnwindInfo]>, } impl CompiledModule { @@ -145,8 +169,16 @@ impl CompiledModule { data: &'data [u8], profiler: &dyn ProfilingAgent, ) -> Result { - let artifacts = CompilationArtifacts::new(compiler, data)?; + let artifacts = CompilationArtifacts::build(compiler, data)?; + Self::from_artifacts(artifacts, compiler.isa(), profiler) + } + /// Creates `CompiledModule` directly from `CompilationArtifacts`. + pub fn from_artifacts( + artifacts: CompilationArtifacts, + isa: &dyn TargetIsa, + profiler: &dyn ProfilingAgent, + ) -> Result { let CompilationArtifacts { module, obj, @@ -155,12 +187,13 @@ impl CompiledModule { traps, stack_maps, address_transform, + debug_info, } = artifacts; // Allocate all of the compiled functions into executable memory, // copying over their contents. let (code_memory, code_range, finished_functions, trampolines) = - build_code_memory(compiler.isa(), &obj, &module, unwind_info).map_err(|message| { + build_code_memory(isa, &obj, &module, &unwind_info).map_err(|message| { SetupError::Instantiate(InstantiationError::Resource(format!( "failed to build code memory for functions: {}", message @@ -168,7 +201,7 @@ impl CompiledModule { })?; // Register GDB JIT images; initialize profiler and load the wasm module. - let dbg_jit_registration = if compiler.tunables().debug_info { + let dbg_jit_registration = if debug_info { let bytes = create_dbg_image(obj.to_vec(), code_range, &module, &finished_functions)?; profiler.module_load(&module, &finished_functions, Some(&bytes)); @@ -194,9 +227,25 @@ impl CompiledModule { traps, stack_maps, address_transform, + obj, + unwind_info, }) } + /// Extracts `CompilationArtifacts` from the compiled module. + pub fn to_compilation_artifacts(&self) -> CompilationArtifacts { + CompilationArtifacts { + module: (*self.module).clone(), + obj: self.obj.clone(), + unwind_info: self.unwind_info.clone(), + data_initializers: self.data_initializers.clone(), + traps: self.traps.clone(), + stack_maps: self.stack_maps.clone(), + address_transform: self.address_transform.clone(), + debug_info: self.code.dbg_jit_registration.is_some(), + } + } + /// Crate an `Instance` from this `CompiledModule`. /// /// Note that if only one instance of this module is needed, it may be more @@ -305,6 +354,7 @@ impl CompiledModule { /// Similar to `DataInitializer`, but owns its own copy of the data rather /// than holding a slice of the original module. +#[derive(Clone, Serialize, Deserialize)] pub struct OwnedDataInitializer { /// The location where the initialization is to be performed. location: DataInitializerLocation, @@ -340,7 +390,7 @@ fn build_code_memory( isa: &dyn TargetIsa, obj: &[u8], module: &Module, - unwind_info: Box<[ObjectUnwindInfo]>, + unwind_info: &Box<[ObjectUnwindInfo]>, ) -> Result< ( CodeMemory, @@ -354,7 +404,7 @@ fn build_code_memory( let mut code_memory = CodeMemory::new(); - let allocation = code_memory.allocate_for_object(&obj, &unwind_info)?; + let allocation = code_memory.allocate_for_object(&obj, unwind_info)?; // Second, create a PrimaryMap from result vector of pointers. let mut finished_functions = PrimaryMap::new(); diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index 212e7522de..ca7d9fde06 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -35,7 +35,7 @@ pub mod trampoline; pub use crate::code_memory::CodeMemory; pub use crate::compiler::{Compilation, CompilationStrategy, Compiler}; -pub use crate::instantiate::{CompiledModule, SetupError}; +pub use crate::instantiate::{CompilationArtifacts, CompiledModule, SetupError}; pub use crate::link::link_module; pub use crate::resolver::{NullResolver, Resolver}; diff --git a/crates/jit/src/object.rs b/crates/jit/src/object.rs index 9e72ef6050..66e3a35209 100644 --- a/crates/jit/src/object.rs +++ b/crates/jit/src/object.rs @@ -3,6 +3,7 @@ use super::trampoline::build_trampoline; use cranelift_frontend::FunctionBuilderContext; use object::write::Object; +use serde::{Deserialize, Serialize}; use wasmtime_debug::DwarfSection; use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa}; @@ -13,7 +14,7 @@ use wasmtime_obj::{ObjectBuilder, ObjectBuilderTarget}; pub use wasmtime_obj::utils; /// Unwind information for object files functions (including trampolines). -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ObjectUnwindInfo { Func(FuncIndex, UnwindInfo), Trampoline(SignatureIndex, UnwindInfo), diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index fa649e48da..935ad6cf82 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -26,6 +26,8 @@ lazy_static = "1.4" log = "0.4.8" wat = { version = "1.0.18", optional = true } smallvec = "1.4.0" +serde = { version = "1.0.94", features = ["derive"] } +bincode = "1.2.1" [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.3.7" diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index c3e7be1313..ca3f6571e5 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -1,10 +1,10 @@ use crate::frame_info::GlobalFrameInfoRegistration; -use crate::runtime::Engine; +use crate::runtime::{Config, Engine}; use crate::types::{EntityType, ExportType, ExternType, ImportType}; -use anyhow::Result; +use anyhow::{bail, Context, Result}; use std::path::Path; use std::sync::{Arc, Mutex}; -use wasmtime_jit::CompiledModule; +use wasmtime_jit::{CompilationArtifacts, CompiledModule}; /// A compiled WebAssembly module, ready to be instantiated. /// @@ -309,6 +309,51 @@ impl Module { }) } + /// Serialize compilation artifacts to the buffer. See also `deseriaize`. + pub fn serialize(&self) -> Result> { + let artifacts = ( + compiler_fingerprint(self.engine.config()), + self.compiled.to_compilation_artifacts(), + ); + + let mut buffer = Vec::new(); + bincode::serialize_into(&mut buffer, &artifacts)?; + + Ok(buffer) + } + + /// Deserializes and creates a module from the compilatio nartifacts. + /// The `serialize` saves the compilation artifacts along with the host + /// fingerprint, which consists of target, compiler flags, and wasmtime + /// package version. + /// + /// The method will fail if fingerprints of current host and serialized + /// one are different. The method does not verify the serialized artifacts + /// for modifications or curruptions. All responsibily of signing and its + /// verification falls on the embedder. + pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result { + let expected_fingerprint = compiler_fingerprint(engine.config()); + + let (fingerprint, artifacts) = + bincode::deserialize_from::<_, (u64, CompilationArtifacts)>(serialized) + .context("Deserialize compilation artifacts")?; + if fingerprint != expected_fingerprint { + bail!("Incompatible compilation artifact"); + } + + let compiled = CompiledModule::from_artifacts( + artifacts, + engine.compiler().isa(), + &*engine.config().profiler, + )?; + + Ok(Module { + engine: engine.clone(), + compiled: Arc::new(compiled), + frame_info_registration: Arc::new(Mutex::new(None)), + }) + } + pub(crate) fn compiled_module(&self) -> &CompiledModule { &self.compiled } @@ -535,6 +580,13 @@ impl Module { } } +fn compiler_fingerprint(config: &Config) -> u64 { + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + config.compiler_fingerprint(&mut hasher); + hasher.finish() +} + fn _assert_send_sync() { fn _assert() {} _assert::(); diff --git a/crates/wasmtime/src/runtime.rs b/crates/wasmtime/src/runtime.rs index 94eea934c9..f46fb253b3 100644 --- a/crates/wasmtime/src/runtime.rs +++ b/crates/wasmtime/src/runtime.rs @@ -10,6 +10,7 @@ use std::hash::{Hash, Hasher}; use std::path::Path; use std::rc::{Rc, Weak}; use std::sync::Arc; +use target_lexicon::Triple; use wasmparser::Validator; use wasmtime_environ::settings::{self, Configurable, SetError}; use wasmtime_environ::{ir, isa, isa::TargetIsa, wasm, CacheConfig, Tunables}; @@ -634,6 +635,22 @@ impl Config { self.tunables.clone(), ) } + + /// Hashes/fingerprints compiler setting to ensure that compatible + /// compilation artifacts are used. + pub(crate) fn compiler_fingerprint(&self, state: &mut H) + where + H: Hasher, + { + self.flags.hash(state); + self.tunables.hash(state); + + let triple = Triple::host(); + triple.hash(state); + + // Catch accidental bugs of reusing across wasmtime versions. + env!("CARGO_PKG_VERSION").hash(state); + } } fn round_up_to_pages(val: u64) -> u64 { diff --git a/examples/serialize.c b/examples/serialize.c new file mode 100644 index 0000000000..6e2407efe0 --- /dev/null +++ b/examples/serialize.c @@ -0,0 +1,169 @@ +/* +Example of instantiating of the WebAssembly module and invoking its exported +function. + +You can compile and run this example on Linux with: + + cargo build --release -p wasmtime + cc examples/hello.c \ + -I crates/c-api/include \ + -I crates/c-api/wasm-c-api/include \ + target/release/libwasmtime.a \ + -lpthread -ldl -lm \ + -o hello + ./hello + +Note that on Windows and macOS the command will be similar, but you'll need +to tweak the `-lpthread` and such annotations as well as the name of the +`libwasmtime.a` file on Windows. +*/ + +#include +#include +#include +#include +#include + +static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap); + +static wasm_trap_t* hello_callback(const wasm_val_t args[], wasm_val_t results[]) { + printf("Calling back...\n"); + printf("> Hello World!\n"); + return NULL; +} + +int serialize(wasm_byte_vec_t* buffer) { + // Set up our compilation context. Note that we could also work with a + // `wasm_config_t` here to configure what feature are enabled and various + // compilation settings. + printf("Initializing...\n"); + wasm_engine_t *engine = wasm_engine_new(); + assert(engine != NULL); + + // Read our input file, which in this case is a wasm text file. + FILE* file = fopen("examples/hello.wat", "r"); + assert(file != NULL); + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t wat; + wasm_byte_vec_new_uninitialized(&wat, file_size); + assert(fread(wat.data, file_size, 1, file) == 1); + fclose(file); + + // Parse the wat into the binary wasm format + wasm_byte_vec_t wasm; + wasmtime_error_t *error = wasmtime_wat2wasm(&wat, &wasm); + if (error != NULL) + exit_with_error("failed to parse wat", error, NULL); + wasm_byte_vec_delete(&wat); + + // Now that we've got our binary webassembly we can compile our module + // and serialize into buffer. + printf("Compiling and serializing module...\n"); + wasm_module_t *module = NULL; + error = wasmtime_module_new(engine, &wasm, &module); + wasm_byte_vec_delete(&wasm); + if (error != NULL) + exit_with_error("failed to compile module", error, NULL); + error = wasmtime_module_serialize(module, buffer); + wasm_module_delete(module); + if (error != NULL) + exit_with_error("failed to serialize module", error, NULL); + + printf("Serialized.\n"); + + wasm_engine_delete(engine); + return 0; +} + +int deserialize(wasm_byte_vec_t* buffer) { + // Set up our compilation context. Note that we could also work with a + // `wasm_config_t` here to configure what feature are enabled and various + // compilation settings. + printf("Initializing...\n"); + wasm_engine_t *engine = wasm_engine_new(); + assert(engine != NULL); + + // With an engine we can create a *store* which is a long-lived group of wasm + // modules. + wasm_store_t *store = wasm_store_new(engine); + assert(store != NULL); + + // Deserialize compiled module. + printf("Deserialize module...\n"); + wasm_module_t *module = NULL; + wasmtime_error_t *error = wasmtime_module_deserialize(engine, buffer, &module); + if (error != NULL) + exit_with_error("failed to compile module", error, NULL); + + // Next up we need to create the function that the wasm module imports. Here + // we'll be hooking up a thunk function to the `hello_callback` native + // function above. + printf("Creating callback...\n"); + wasm_functype_t *hello_ty = wasm_functype_new_0_0(); + wasm_func_t *hello = wasm_func_new(store, hello_ty, hello_callback); + + // With our callback function we can now instantiate the compiled module, + // giving us an instance we can then execute exports from. Note that + // instantiation can trap due to execution of the `start` function, so we need + // to handle that here too. + printf("Instantiating module...\n"); + wasm_trap_t *trap = NULL; + wasm_instance_t *instance = NULL; + const wasm_extern_t *imports[] = { wasm_func_as_extern(hello) }; + error = wasmtime_instance_new(store, module, imports, 1, &instance, &trap); + if (instance == NULL) + exit_with_error("failed to instantiate", error, trap); + + // Lookup our `run` export function + printf("Extracting export...\n"); + wasm_extern_vec_t externs; + wasm_instance_exports(instance, &externs); + assert(externs.size == 1); + wasm_func_t *run = wasm_extern_as_func(externs.data[0]); + assert(run != NULL); + + // And call it! + printf("Calling export...\n"); + error = wasmtime_func_call(run, NULL, 0, NULL, 0, &trap); + if (error != NULL || trap != NULL) + exit_with_error("failed to call function", error, trap); + + // Clean up after ourselves at this point + printf("All finished!\n"); + + wasm_extern_vec_delete(&externs); + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_store_delete(store); + wasm_engine_delete(engine); + return 0; +} + +int main() { + wasm_byte_vec_t buffer; + if (serialize(&buffer)) { + return 1; + } + if (deserialize(&buffer)) { + return 1; + } + wasm_byte_vec_delete(&buffer); + return 0; +} + +static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) { + fprintf(stderr, "error: %s\n", message); + wasm_byte_vec_t error_message; + if (error != NULL) { + wasmtime_error_message(error, &error_message); + wasmtime_error_delete(error); + } else { + wasm_trap_message(trap, &error_message); + wasm_trap_delete(trap); + } + fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data); + wasm_byte_vec_delete(&error_message); + exit(1); +} diff --git a/examples/serialize.rs b/examples/serialize.rs new file mode 100644 index 0000000000..ce69c76057 --- /dev/null +++ b/examples/serialize.rs @@ -0,0 +1,69 @@ +//! Small example of how to serialize compiled wasm module to the disk, +//! and then instantiate it from the compilation artifacts. + +// You can execute this example with `cargo run --example serialize` + +use anyhow::Result; +use wasmtime::*; + +fn serialize() -> Result> { + // Configure the initial compilation environment, creating the global + // `Store` structure. Note that you can also tweak configuration settings + // with a `Config` and an `Engine` if desired. + println!("Initializing..."); + let engine = Engine::default(); + + // Compile the wasm binary into an in-memory instance of a `Module`. + println!("Compiling module..."); + let module = Module::from_file(&engine, "examples/hello.wat")?; + let serialized = module.serialize()?; + + println!("Serialized."); + Ok(serialized) +} + +fn deserialize(buffer: &[u8]) -> Result<()> { + // Configure the initial compilation environment, creating the global + // `Store` structure. Note that you can also tweak configuration settings + // with a `Config` and an `Engine` if desired. + println!("Initializing..."); + let store = Store::default(); + + // Compile the wasm binary into an in-memory instance of a `Module`. + println!("Deserialize module..."); + let module = Module::deserialize(store.engine(), buffer)?; + + // Here we handle the imports of the module, which in this case is our + // `HelloCallback` type and its associated implementation of `Callback. + println!("Creating callback..."); + let hello_func = Func::wrap(&store, || { + println!("Calling back..."); + println!("> Hello World!"); + }); + + // Once we've got that all set up we can then move to the instantiation + // phase, pairing together a compiled module as well as a set of imports. + // Note that this is where the wasm `start` function, if any, would run. + println!("Instantiating module..."); + let imports = [hello_func.into()]; + let instance = Instance::new(&store, &module, &imports)?; + + // Next we poke around a bit to extract the `run` function from the module. + println!("Extracting export..."); + let run = instance + .get_func("run") + .ok_or(anyhow::format_err!("failed to find `run` function export"))? + .get0::<()>()?; + + // And last but not least we can call it! + println!("Calling export..."); + run()?; + + println!("Done."); + Ok(()) +} + +fn main() -> Result<()> { + let file = serialize()?; + deserialize(&file) +} diff --git a/tests/all/main.rs b/tests/all/main.rs index e8f952ff74..ff4c452292 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -12,6 +12,7 @@ mod instance; mod invoke_func_via_table; mod linker; mod memory_creator; +mod module_serialize; mod name; mod stack_overflow; mod table; diff --git a/tests/all/module_serialize.rs b/tests/all/module_serialize.rs new file mode 100644 index 0000000000..d46beb40c2 --- /dev/null +++ b/tests/all/module_serialize.rs @@ -0,0 +1,48 @@ +use anyhow::{bail, Result}; +use wasmtime::*; + +fn serialize(engine: &Engine, wat: &'static str) -> Result> { + let module = Module::new(&engine, wat)?; + Ok(module.serialize()?) +} + +fn deserialize_and_instantiate(store: &Store, buffer: &[u8]) -> Result { + let module = Module::deserialize(store.engine(), buffer)?; + Ok(Instance::new(&store, &module, &[])?) +} + +#[test] +fn test_module_serialize_simple() -> Result<()> { + let buffer = serialize( + &Engine::default(), + "(module (func (export \"run\") (result i32) i32.const 42))", + )?; + + let store = Store::default(); + let instance = deserialize_and_instantiate(&store, &buffer)?; + let run = instance + .get_func("run") + .ok_or(anyhow::format_err!("failed to find `run` function export"))? + .get0::()?; + let result = run()?; + + assert_eq!(42, result); + Ok(()) +} + +#[test] +fn test_module_serialize_fail() -> Result<()> { + let buffer = serialize( + &Engine::default(), + "(module (func (export \"run\") (result i32) i32.const 42))", + )?; + + let mut config = Config::new(); + config.cranelift_opt_level(OptLevel::None); + let store = Store::new(&Engine::new(&config)); + match deserialize_and_instantiate(&store, &buffer) { + Ok(_) => bail!("expected failure at deserialization"), + Err(_) => (), + } + Ok(()) +}