Skip to content

Commit b0d42e7

Browse files
committed
Code review feedback.
* Move `Module::compile` to `Engine::precompile_module`. * Remove `Module::deserialize` method. * Make `Module::serialize` the same format as `Engine::precompile_module`. * Make `Engine::precompile_module` return a `Vec<u8>`. * Move the remaining serialization-related code to `serialization.rs`.
1 parent cb77dfd commit b0d42e7

10 files changed

Lines changed: 136 additions & 146 deletions

File tree

RELEASES.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@
66

77
### Added
88

9-
* The `wasmtime compile` command was added to support AOT compilation of Wasm
10-
modules.
9+
* Added the `wasmtime compile` command to support AOT compilation of Wasm modules.
1110

12-
* The `Module::compile` method was added to support AOT compilation of a module.
11+
* Added the `Engine::precompile_module` method to support AOT module compilation.
1312

1413
* Added the `Config::target` method to change the compilation target of the
15-
configuration. This can be used in conjunction with `Module::compile` to target
16-
a different host triple than the current one.
14+
configuration. This can be used in conjunction with `Engine::precompile_module`
15+
to target a different host triple than the current one.
1716

1817
* Added the `Config::cranelift_flag_enable` method to enable setting Cranelift
1918
boolean flags or presets in a config.
@@ -26,6 +25,8 @@
2625
singular `--wasm-features` option. The previous options are still supported, but
2726
are not displayed in help text.
2827

28+
* Breaking: `Module::deserialize` has been removed in favor of `Module::new`.
29+
2930
* Breaking: `Config::cranelift_clear_cpu_flags` was removed. Use `Config::target`
3031
to clear the CPU flags for the host's target.
3132

@@ -42,10 +43,6 @@
4243
* Breaking: the CLI option `--enable-bulk-memory=false` has been changed to
4344
`--wasm-features=-bulk-memory`.
4445

45-
* Modules serialized with `Module::serialize` can now be deserialized with
46-
`Module::deserialize` on a compatible host that does not have to match the
47-
original environment exactly.
48-
4946
## 0.25.0
5047

5148
Released 2021-03-16.

crates/c-api/src/module.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,10 @@ pub extern "C" fn wasmtime_module_deserialize(
185185
binary: &wasm_byte_vec_t,
186186
ret: &mut *mut wasm_module_t,
187187
) -> Option<Box<wasmtime_error_t>> {
188-
handle_result(
189-
Module::deserialize(&engine.engine, binary.as_slice()),
190-
|module| {
191-
let module = Box::new(wasm_module_t::new(module));
192-
*ret = Box::into_raw(module);
193-
},
194-
)
188+
handle_result(Module::new(&engine.engine, binary.as_slice()), |module| {
189+
let module = Box::new(wasm_module_t::new(module));
190+
*ret = Box::into_raw(module);
191+
})
195192
}
196193

197194
#[no_mangle]

crates/wasmtime/src/engine.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,34 @@ impl Engine {
7878
pub fn same(a: &Engine, b: &Engine) -> bool {
7979
Arc::ptr_eq(&a.inner, &b.inner)
8080
}
81+
82+
/// Ahead-of-time (AOT) compiles a WebAssembly module.
83+
///
84+
/// The `bytes` provided must be in one of two formats:
85+
///
86+
/// * A [binary-encoded][binary] WebAssembly module. This is always supported.
87+
/// * A [text-encoded][text] instance of the WebAssembly text format.
88+
/// This is only supported when the `wat` feature of this crate is enabled.
89+
/// If this is supplied then the text format will be parsed before validation.
90+
/// Note that the `wat` feature is enabled by default.
91+
///
92+
/// [binary]: https://webassembly.github.io/spec/core/binary/index.html
93+
/// [text]: https://webassembly.github.io/spec/core/text/index.html
94+
pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> {
95+
const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux"));
96+
97+
#[cfg(feature = "wat")]
98+
let bytes = wat::parse_bytes(&bytes)?;
99+
100+
let (_, artifacts, types) = wasmtime_jit::CompilationArtifacts::build(
101+
&self.inner.compiler,
102+
&bytes,
103+
USE_PAGED_MEM_INIT,
104+
)?;
105+
106+
crate::module::SerializedModule::from_artifacts(&self.inner.compiler, &artifacts, &types)
107+
.to_bytes()
108+
}
81109
}
82110

83111
impl Default for Engine {

crates/wasmtime/src/module.rs

Lines changed: 15 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
use crate::types::{ExportType, ExternType, ImportType};
22
use crate::{Engine, ModuleType};
33
use anyhow::{bail, Context, Result};
4-
use bincode::Options;
54
use std::fs;
6-
use std::io::Write;
75
use std::path::Path;
86
use std::sync::Arc;
97
use wasmparser::Validator;
@@ -15,9 +13,7 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables};
1513

1614
mod serialization;
1715

18-
use serialization::SerializedModule;
19-
20-
const COMPILED_MODULE_HEADER: &[u8] = b"\0wasmtime-aot";
16+
pub use serialization::SerializedModule;
2117

2218
/// A compiled WebAssembly module, ready to be instantiated.
2319
///
@@ -111,14 +107,16 @@ struct ModuleInner {
111107
impl Module {
112108
/// Creates a new WebAssembly `Module` from the given in-memory `bytes`.
113109
///
114-
/// The `bytes` provided must be in one of three formats:
110+
/// The `bytes` provided must be in one of the following formats:
115111
///
116112
/// * A [binary-encoded][binary] WebAssembly module. This is always supported.
117113
/// * A [text-encoded][text] instance of the WebAssembly text format.
118114
/// This is only supported when the `wat` feature of this crate is enabled.
119115
/// If this is supplied then the text format will be parsed before validation.
120116
/// Note that the `wat` feature is enabled by default.
121-
/// * A module compiled with [`Module::compile`] or the `wasmtime compile` command.
117+
/// * A module serialized with [`Module::serialize`].
118+
/// * A module compiled with [`Engine::precompile_module`] or the
119+
/// `wasmtime compile` command.
122120
///
123121
/// The data for the wasm module must be loaded in-memory if it's present
124122
/// elsewhere, for example on disk. This requires that the entire binary is
@@ -177,8 +175,9 @@ impl Module {
177175
/// ```
178176
pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Module> {
179177
let bytes = bytes.as_ref();
180-
if bytes.starts_with(COMPILED_MODULE_HEADER) {
181-
return Self::deserialize(engine, &bytes[COMPILED_MODULE_HEADER.len()..]);
178+
179+
if let Some(module) = SerializedModule::from_bytes(bytes)? {
180+
return module.into_module(engine);
182181
}
183182

184183
#[cfg(feature = "wat")]
@@ -267,8 +266,8 @@ impl Module {
267266
/// # }
268267
/// ```
269268
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
270-
if binary.starts_with(COMPILED_MODULE_HEADER) {
271-
return Self::deserialize(engine, &binary[COMPILED_MODULE_HEADER.len()..]);
269+
if let Some(module) = SerializedModule::from_bytes(binary)? {
270+
return module.into_module(engine);
272271
}
273272

274273
// Check to see that the config's target matches the host
@@ -344,41 +343,6 @@ impl Module {
344343
Ok(())
345344
}
346345

347-
/// Ahead-of-time (AOT) compiles a WebAssembly module.
348-
///
349-
/// The `bytes` provided must be in one of two formats:
350-
///
351-
/// * A [binary-encoded][binary] WebAssembly module. This is always supported.
352-
/// * A [text-encoded][text] instance of the WebAssembly text format.
353-
/// This is only supported when the `wat` feature of this crate is enabled.
354-
/// If this is supplied then the text format will be parsed before validation.
355-
/// Note that the `wat` feature is enabled by default.
356-
///
357-
/// See [`Module::new`] for errors that may be returned by this function.
358-
///
359-
/// [binary]: https://webassembly.github.io/spec/core/binary/index.html
360-
/// [text]: https://webassembly.github.io/spec/core/text/index.html
361-
pub fn compile(engine: &Engine, bytes: &[u8], mut output: impl Write) -> Result<()> {
362-
const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux"));
363-
364-
if bytes.starts_with(COMPILED_MODULE_HEADER) {
365-
bail!("input is already a compiled module");
366-
}
367-
368-
#[cfg(feature = "wat")]
369-
let bytes = wat::parse_bytes(&bytes)?;
370-
371-
let (_, artifacts, types) =
372-
CompilationArtifacts::build(engine.compiler(), &bytes, USE_PAGED_MEM_INIT)?;
373-
374-
// Write a header that marks this as a compiled module
375-
output.write_all(COMPILED_MODULE_HEADER)?;
376-
Self::serialize_module(
377-
&SerializedModule::from_artifacts(engine.compiler(), &artifacts, &types),
378-
output,
379-
)
380-
}
381-
382346
/// Returns the type signature of this module.
383347
pub fn ty(&self) -> ModuleType {
384348
let mut sig = ModuleType::new();
@@ -396,58 +360,12 @@ impl Module {
396360
sig
397361
}
398362

399-
/// Serialize compilation artifacts to the buffer. See also `deserialize`.
363+
/// Serialize the module to a vector of bytes.
364+
///
365+
/// Use `Module::new` or `Module::from_binary` to create the module
366+
/// from the bytes.
400367
pub fn serialize(&self) -> Result<Vec<u8>> {
401-
let mut buffer = Vec::new();
402-
Self::serialize_module(&SerializedModule::new(self), &mut buffer)?;
403-
Ok(buffer)
404-
}
405-
406-
fn serialize_module(module: &SerializedModule, mut output: impl Write) -> Result<()> {
407-
// Preface the data with a version so we can do a version check independent
408-
// of the serialized data.
409-
let version = env!("CARGO_PKG_VERSION");
410-
assert!(
411-
version.len() < 256,
412-
"package version must be less than 256 bytes"
413-
);
414-
output.write(&[version.len() as u8])?;
415-
output.write_all(version.as_bytes())?;
416-
bincode_options().serialize_into(output, module)?;
417-
Ok(())
418-
}
419-
420-
/// Deserializes and creates a module from the compilation artifacts.
421-
/// The `serialize` saves the compilation artifacts along with the host
422-
/// fingerprint, which consists of target, compiler flags, and wasmtime
423-
/// package version.
424-
///
425-
/// The method will fail if fingerprints of current host and serialized
426-
/// one are different. The method does not verify the serialized artifacts
427-
/// for modifications or corruptions. All responsibly of signing and its
428-
/// verification falls on the embedder.
429-
pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result<Module> {
430-
if serialized.is_empty() {
431-
bail!("serialized data data is empty");
432-
}
433-
434-
let version_len = serialized[0] as usize;
435-
if serialized.len() < version_len + 1 {
436-
bail!("serialized data is malformed");
437-
}
438-
439-
let version = std::str::from_utf8(&serialized[1..1 + version_len])?;
440-
if version != env!("CARGO_PKG_VERSION") {
441-
bail!(
442-
"Module was compiled with incompatible Wasmtime version '{}'",
443-
version
444-
);
445-
}
446-
447-
bincode_options()
448-
.deserialize::<SerializedModule<'_>>(&serialized[1 + version_len..])
449-
.context("Deserialize compilation artifacts")?
450-
.into_module(engine)
368+
SerializedModule::new(self).to_bytes()
451369
}
452370

453371
/// Creates a submodule `Module` value from the specified parameters.
@@ -732,17 +650,6 @@ impl Module {
732650
}
733651
}
734652

735-
fn bincode_options() -> impl Options {
736-
// Use a variable-length integer encoding instead of fixed length. The
737-
// module shown on #2318 gets compressed from ~160MB to ~110MB simply using
738-
// this, presumably because there's a lot of 8-byte integers which generally
739-
// have small values. Local testing shows that the deserialization
740-
// performance, while higher, is in the few-percent range. For huge size
741-
// savings this seems worthwhile to lose a small percentage of
742-
// deserialization performance.
743-
bincode::DefaultOptions::new().with_varint_encoding()
744-
}
745-
746653
fn _assert_send_sync() {
747654
fn _assert<T: Send + Sync>() {}
748655
_assert::<Module>();

crates/wasmtime/src/module/serialization.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
33
use super::ModuleInner;
44
use crate::{Engine, Module, OptLevel};
5-
use anyhow::{anyhow, bail, Result};
5+
use anyhow::{anyhow, bail, Context, Result};
6+
use bincode::Options;
67
use serde::{Deserialize, Serialize};
78
use std::collections::HashMap;
89
use std::hash::{Hash, Hasher};
@@ -14,6 +15,19 @@ use wasmtime_jit::{
1415
CompilationArtifacts, CompilationStrategy, CompiledModule, Compiler, TypeTables,
1516
};
1617

18+
const HEADER: &[u8] = b"\0wasmtime-aot";
19+
20+
fn bincode_options() -> impl Options {
21+
// Use a variable-length integer encoding instead of fixed length. The
22+
// module shown on #2318 gets compressed from ~160MB to ~110MB simply using
23+
// this, presumably because there's a lot of 8-byte integers which generally
24+
// have small values. Local testing shows that the deserialization
25+
// performance, while higher, is in the few-percent range. For huge size
26+
// savings this seems worthwhile to lose a small percentage of
27+
// deserialization performance.
28+
bincode::DefaultOptions::new().with_varint_encoding()
29+
}
30+
1731
// This exists because `wasmparser::WasmFeatures` isn't serializable
1832
#[derive(Hash, Debug, Copy, Clone, Serialize, Deserialize)]
1933
struct WasmFeatures {
@@ -273,6 +287,60 @@ impl<'a> SerializedModule<'a> {
273287
}
274288
}
275289

290+
pub fn to_bytes(&self) -> Result<Vec<u8>> {
291+
use std::io::Write;
292+
293+
let mut bytes = Vec::new();
294+
295+
bytes.write_all(HEADER)?;
296+
297+
// Preface the data with a version so we can do a version check independent
298+
// of the serialized data.
299+
let version = env!("CARGO_PKG_VERSION");
300+
assert!(
301+
version.len() < 256,
302+
"package version must be less than 256 bytes"
303+
);
304+
bytes.write(&[version.len() as u8])?;
305+
306+
bytes.write_all(version.as_bytes())?;
307+
308+
bincode_options().serialize_into(&mut bytes, self)?;
309+
310+
Ok(bytes)
311+
}
312+
313+
pub fn from_bytes(bytes: &[u8]) -> Result<Option<Self>> {
314+
if !bytes.starts_with(HEADER) {
315+
return Ok(None);
316+
}
317+
318+
let bytes = &bytes[HEADER.len()..];
319+
320+
if bytes.is_empty() {
321+
bail!("serialized data data is empty");
322+
}
323+
324+
let version_len = bytes[0] as usize;
325+
if bytes.len() < version_len + 1 {
326+
bail!("serialized data is malformed");
327+
}
328+
329+
let version = std::str::from_utf8(&bytes[1..1 + version_len])?;
330+
if version != env!("CARGO_PKG_VERSION") {
331+
bail!(
332+
"Module was compiled with incompatible Wasmtime version '{}'",
333+
version
334+
);
335+
}
336+
337+
Ok(Some(
338+
bincode_options()
339+
.deserialize::<SerializedModule<'_>>(&bytes[1 + version_len..])
340+
.context("deserialize compilation artifacts")?,
341+
))
342+
}
343+
276344
fn check_triple(&self, isa: &dyn TargetIsa) -> Result<()> {
277345
let triple = target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
278346

examples/serialize.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn deserialize(buffer: &[u8]) -> Result<()> {
3131

3232
// Compile the wasm binary into an in-memory instance of a `Module`.
3333
println!("Deserialize module...");
34-
let module = Module::deserialize(store.engine(), buffer)?;
34+
let module = Module::new(store.engine(), buffer)?;
3535

3636
// Here we handle the imports of the module, which in this case is our
3737
// `HelloCallback` type and its associated implementation of `Callback.

0 commit comments

Comments
 (0)