Serialize and deserialize compilation artifacts. (#2020)

* Serialize and deserialize Module
* Use bincode to serialize
* Add wasm_module_serialize; docs
* Simple tests
This commit is contained in:
Yury Delendik
2020-07-21 15:05:50 -05:00
committed by GitHub
parent c420f65214
commit 399ee0a54c
17 changed files with 528 additions and 20 deletions

3
Cargo.lock generated
View File

@@ -2361,12 +2361,14 @@ version = "0.19.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"backtrace", "backtrace",
"bincode",
"cfg-if", "cfg-if",
"lazy_static", "lazy_static",
"libc", "libc",
"log", "log",
"region", "region",
"rustc-demangle", "rustc-demangle",
"serde",
"smallvec", "smallvec",
"target-lexicon", "target-lexicon",
"tempfile", "tempfile",
@@ -2529,6 +2531,7 @@ dependencies = [
"more-asserts", "more-asserts",
"object 0.20.0", "object 0.20.0",
"region", "region",
"serde",
"target-lexicon", "target-lexicon",
"thiserror", "thiserror",
"wasmparser 0.59.0", "wasmparser 0.59.0",

View File

@@ -45,7 +45,7 @@ pub trait Configurable {
} }
/// Collect settings values based on a template. /// Collect settings values based on a template.
#[derive(Clone)] #[derive(Clone, Hash)]
pub struct Builder { pub struct Builder {
template: &'static detail::Template, template: &'static detail::Template,
bytes: Box<[u8]>, bytes: Box<[u8]>,
@@ -212,8 +212,10 @@ impl<'a> PredicateView<'a> {
pub mod detail { pub mod detail {
use crate::constant_hash; use crate::constant_hash;
use core::fmt; use core::fmt;
use core::hash::Hash;
/// An instruction group template. /// An instruction group template.
#[derive(Hash)]
pub struct Template { pub struct Template {
/// Name of the instruction group. /// Name of the instruction group.
pub name: &'static str, 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. /// 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. /// Each settings group will be represented as a constant DESCRIPTORS array.
#[derive(Hash)]
pub struct Descriptor { pub struct Descriptor {
/// Lower snake-case name of setting as defined in meta. /// Lower snake-case name of setting as defined in meta.
pub name: &'static str, 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. /// The different kind of settings along with descriptor bits that depend on the kind.
#[derive(Clone, Copy)] #[derive(Clone, Copy, Hash)]
pub enum Detail { pub enum Detail {
/// A boolean setting only uses one bit, numbered from LSB. /// A boolean setting only uses one bit, numbered from LSB.
Bool { Bool {

View File

@@ -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); 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 #undef own
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -130,3 +130,63 @@ pub extern "C" fn wasm_module_obtain(
exports, 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<Box<wasm_module_t>> {
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<Box<wasmtime_error_t>> {
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<Box<wasmtime_error_t>> {
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::<Vec<_>>();
let exports = module
.exports()
.map(|e| wasm_exporttype_t::new(e.name().to_owned(), e.ty()))
.collect::<Vec<_>>();
let module = Box::new(wasm_module_t {
module: module,
imports,
exports,
});
*ret = Box::into_raw(module);
},
)
}

View File

@@ -137,10 +137,10 @@ impl TablePlan {
/// A translated WebAssembly module, excluding the function bodies and /// A translated WebAssembly module, excluding the function bodies and
/// memory initializers. /// memory initializers.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Module { pub struct Module {
/// A unique identifier (within this process) for this 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, pub id: usize,
/// The name of this wasm module, often found in the wasm file. /// 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`, /// 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 /// and is used as part of the cache key when we load compiled modules from the
/// global cache. /// global cache.
#[derive(Debug, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Hash, Serialize, Deserialize)]
pub struct ModuleLocal { pub struct ModuleLocal {
/// Unprocessed signatures exactly as provided by `declare_signature()`. /// Unprocessed signatures exactly as provided by `declare_signature()`.
pub signatures: PrimaryMap<SignatureIndex, (WasmFuncType, ir::Signature)>, pub signatures: PrimaryMap<SignatureIndex, (WasmFuncType, ir::Signature)>,

View File

@@ -9,6 +9,7 @@ use cranelift_wasm::{
Memory, MemoryIndex, ModuleTranslationState, SignatureIndex, Table, TableIndex, Memory, MemoryIndex, ModuleTranslationState, SignatureIndex, Table, TableIndex,
TargetEnvironment, WasmError, WasmFuncType, WasmResult, TargetEnvironment, WasmError, WasmFuncType, WasmResult,
}; };
use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::sync::Arc; 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 /// A memory index and offset within that memory where a data initialization
/// should is to be performed. /// should is to be performed.
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct DataInitializerLocation { pub struct DataInitializerLocation {
/// The index of the memory to initialize. /// The index of the memory to initialize.
pub memory_index: MemoryIndex, pub memory_index: MemoryIndex,

View File

@@ -32,6 +32,7 @@ cfg-if = "0.1.9"
log = "0.4" log = "0.4"
gimli = { version = "0.21.0", default-features = false, features = ["write"] } gimli = { version = "0.21.0", default-features = false, features = ["write"] }
object = { version = "0.20", 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] [target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.8", features = ["winnt", "impl-default"] } winapi = { version = "0.3.8", features = ["winnt", "impl-default"] }

View File

@@ -10,6 +10,7 @@ use crate::link::link_module;
use crate::object::ObjectUnwindInfo; use crate::object::ObjectUnwindInfo;
use crate::resolver::Resolver; use crate::resolver::Resolver;
use object::File as ObjectFile; use object::File as ObjectFile;
use serde::{Deserialize, Serialize};
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@@ -51,27 +52,47 @@ pub enum SetupError {
DebugInfo(#[from] anyhow::Error), DebugInfo(#[from] anyhow::Error),
} }
// Contains all compilation artifacts. /// Contains all compilation artifacts.
struct CompilationArtifacts { #[derive(Serialize, Deserialize)]
pub struct CompilationArtifacts {
/// Module metadata.
module: Module, module: Module,
/// ELF image with functions code.
obj: Box<[u8]>, obj: Box<[u8]>,
/// Unwind information for function code.
unwind_info: Box<[ObjectUnwindInfo]>, unwind_info: Box<[ObjectUnwindInfo]>,
/// Data initiailizers.
data_initializers: Box<[OwnedDataInitializer]>, data_initializers: Box<[OwnedDataInitializer]>,
/// Traps descriptors.
traps: Traps, traps: Traps,
/// Stack map descriptors.
stack_maps: StackMaps, stack_maps: StackMaps,
/// Wasm to function code address map.
address_transform: ModuleAddressMap, address_transform: ModuleAddressMap,
/// Debug info presence flags.
debug_info: bool,
} }
impl CompilationArtifacts { impl CompilationArtifacts {
fn new(compiler: &Compiler, data: &[u8]) -> Result<Self, SetupError> { /// Builds compilation artifacts.
pub fn build(compiler: &Compiler, data: &[u8]) -> Result<Self, SetupError> {
let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables()); let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables());
let translation = environ let translation = environ
.translate(data) .translate(data)
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
let debug_info = compiler.tunables().debug_info;
let mut debug_data = None; let mut debug_data = None;
if compiler.tunables().debug_info { if debug_info {
// TODO Do we want to ignore invalid DWARF data? // TODO Do we want to ignore invalid DWARF data?
debug_data = Some(read_debuginfo(&data)?); debug_data = Some(read_debuginfo(&data)?);
} }
@@ -110,6 +131,7 @@ impl CompilationArtifacts {
traps, traps,
stack_maps, stack_maps,
address_transform, address_transform,
debug_info,
}) })
} }
} }
@@ -136,6 +158,8 @@ pub struct CompiledModule {
traps: Traps, traps: Traps,
stack_maps: StackMaps, stack_maps: StackMaps,
address_transform: ModuleAddressMap, address_transform: ModuleAddressMap,
obj: Box<[u8]>,
unwind_info: Box<[ObjectUnwindInfo]>,
} }
impl CompiledModule { impl CompiledModule {
@@ -145,8 +169,16 @@ impl CompiledModule {
data: &'data [u8], data: &'data [u8],
profiler: &dyn ProfilingAgent, profiler: &dyn ProfilingAgent,
) -> Result<Self, SetupError> { ) -> Result<Self, SetupError> {
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<Self, SetupError> {
let CompilationArtifacts { let CompilationArtifacts {
module, module,
obj, obj,
@@ -155,12 +187,13 @@ impl CompiledModule {
traps, traps,
stack_maps, stack_maps,
address_transform, address_transform,
debug_info,
} = artifacts; } = artifacts;
// Allocate all of the compiled functions into executable memory, // Allocate all of the compiled functions into executable memory,
// copying over their contents. // copying over their contents.
let (code_memory, code_range, finished_functions, trampolines) = 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!( SetupError::Instantiate(InstantiationError::Resource(format!(
"failed to build code memory for functions: {}", "failed to build code memory for functions: {}",
message message
@@ -168,7 +201,7 @@ impl CompiledModule {
})?; })?;
// Register GDB JIT images; initialize profiler and load the wasm module. // 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)?; let bytes = create_dbg_image(obj.to_vec(), code_range, &module, &finished_functions)?;
profiler.module_load(&module, &finished_functions, Some(&bytes)); profiler.module_load(&module, &finished_functions, Some(&bytes));
@@ -194,9 +227,25 @@ impl CompiledModule {
traps, traps,
stack_maps, stack_maps,
address_transform, 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`. /// Crate an `Instance` from this `CompiledModule`.
/// ///
/// Note that if only one instance of this module is needed, it may be more /// 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 /// Similar to `DataInitializer`, but owns its own copy of the data rather
/// than holding a slice of the original module. /// than holding a slice of the original module.
#[derive(Clone, Serialize, Deserialize)]
pub struct OwnedDataInitializer { pub struct OwnedDataInitializer {
/// The location where the initialization is to be performed. /// The location where the initialization is to be performed.
location: DataInitializerLocation, location: DataInitializerLocation,
@@ -340,7 +390,7 @@ fn build_code_memory(
isa: &dyn TargetIsa, isa: &dyn TargetIsa,
obj: &[u8], obj: &[u8],
module: &Module, module: &Module,
unwind_info: Box<[ObjectUnwindInfo]>, unwind_info: &Box<[ObjectUnwindInfo]>,
) -> Result< ) -> Result<
( (
CodeMemory, CodeMemory,
@@ -354,7 +404,7 @@ fn build_code_memory(
let mut code_memory = CodeMemory::new(); 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. // Second, create a PrimaryMap from result vector of pointers.
let mut finished_functions = PrimaryMap::new(); let mut finished_functions = PrimaryMap::new();

View File

@@ -35,7 +35,7 @@ pub mod trampoline;
pub use crate::code_memory::CodeMemory; pub use crate::code_memory::CodeMemory;
pub use crate::compiler::{Compilation, CompilationStrategy, Compiler}; 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::link::link_module;
pub use crate::resolver::{NullResolver, Resolver}; pub use crate::resolver::{NullResolver, Resolver};

View File

@@ -3,6 +3,7 @@
use super::trampoline::build_trampoline; use super::trampoline::build_trampoline;
use cranelift_frontend::FunctionBuilderContext; use cranelift_frontend::FunctionBuilderContext;
use object::write::Object; use object::write::Object;
use serde::{Deserialize, Serialize};
use wasmtime_debug::DwarfSection; use wasmtime_debug::DwarfSection;
use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa}; use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
@@ -13,7 +14,7 @@ use wasmtime_obj::{ObjectBuilder, ObjectBuilderTarget};
pub use wasmtime_obj::utils; pub use wasmtime_obj::utils;
/// Unwind information for object files functions (including trampolines). /// Unwind information for object files functions (including trampolines).
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ObjectUnwindInfo { pub enum ObjectUnwindInfo {
Func(FuncIndex, UnwindInfo), Func(FuncIndex, UnwindInfo),
Trampoline(SignatureIndex, UnwindInfo), Trampoline(SignatureIndex, UnwindInfo),

View File

@@ -26,6 +26,8 @@ lazy_static = "1.4"
log = "0.4.8" log = "0.4.8"
wat = { version = "1.0.18", optional = true } wat = { version = "1.0.18", optional = true }
smallvec = "1.4.0" smallvec = "1.4.0"
serde = { version = "1.0.94", features = ["derive"] }
bincode = "1.2.1"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
winapi = "0.3.7" winapi = "0.3.7"

View File

@@ -1,10 +1,10 @@
use crate::frame_info::GlobalFrameInfoRegistration; use crate::frame_info::GlobalFrameInfoRegistration;
use crate::runtime::Engine; use crate::runtime::{Config, Engine};
use crate::types::{EntityType, ExportType, ExternType, ImportType}; use crate::types::{EntityType, ExportType, ExternType, ImportType};
use anyhow::Result; use anyhow::{bail, Context, Result};
use std::path::Path; use std::path::Path;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use wasmtime_jit::CompiledModule; use wasmtime_jit::{CompilationArtifacts, CompiledModule};
/// A compiled WebAssembly module, ready to be instantiated. /// 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<Vec<u8>> {
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<Module> {
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 { pub(crate) fn compiled_module(&self) -> &CompiledModule {
&self.compiled &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_send_sync() {
fn _assert<T: Send + Sync>() {} fn _assert<T: Send + Sync>() {}
_assert::<Module>(); _assert::<Module>();

View File

@@ -10,6 +10,7 @@ use std::hash::{Hash, Hasher};
use std::path::Path; use std::path::Path;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::sync::Arc; use std::sync::Arc;
use target_lexicon::Triple;
use wasmparser::Validator; use wasmparser::Validator;
use wasmtime_environ::settings::{self, Configurable, SetError}; use wasmtime_environ::settings::{self, Configurable, SetError};
use wasmtime_environ::{ir, isa, isa::TargetIsa, wasm, CacheConfig, Tunables}; use wasmtime_environ::{ir, isa, isa::TargetIsa, wasm, CacheConfig, Tunables};
@@ -634,6 +635,22 @@ impl Config {
self.tunables.clone(), self.tunables.clone(),
) )
} }
/// Hashes/fingerprints compiler setting to ensure that compatible
/// compilation artifacts are used.
pub(crate) fn compiler_fingerprint<H>(&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 { fn round_up_to_pages(val: u64) -> u64 {

169
examples/serialize.c Normal file
View File

@@ -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 <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <wasm.h>
#include <wasmtime.h>
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);
}

69
examples/serialize.rs Normal file
View File

@@ -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<Vec<u8>> {
// 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)
}

View File

@@ -12,6 +12,7 @@ mod instance;
mod invoke_func_via_table; mod invoke_func_via_table;
mod linker; mod linker;
mod memory_creator; mod memory_creator;
mod module_serialize;
mod name; mod name;
mod stack_overflow; mod stack_overflow;
mod table; mod table;

View File

@@ -0,0 +1,48 @@
use anyhow::{bail, Result};
use wasmtime::*;
fn serialize(engine: &Engine, wat: &'static str) -> Result<Vec<u8>> {
let module = Module::new(&engine, wat)?;
Ok(module.serialize()?)
}
fn deserialize_and_instantiate(store: &Store, buffer: &[u8]) -> Result<Instance> {
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::<i32>()?;
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(())
}