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:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<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);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<SignatureIndex, (WasmFuncType, ir::Signature)>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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<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 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<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 {
|
||||
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();
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<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 {
|
||||
&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<T: Send + Sync>() {}
|
||||
_assert::<Module>();
|
||||
|
||||
@@ -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<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 {
|
||||
|
||||
169
examples/serialize.c
Normal file
169
examples/serialize.c
Normal 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
69
examples/serialize.rs
Normal 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)
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
48
tests/all/module_serialize.rs
Normal file
48
tests/all/module_serialize.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user