Skip to content

Commit

Permalink
Extract versioning and add wasmer module version to folder name
Browse files Browse the repository at this point in the history
  • Loading branch information
webmaster128 committed Feb 17, 2022
1 parent 1cd7ac9 commit 794b174
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 31 deletions.
53 changes: 22 additions & 31 deletions packages/vm/src/modules/file_system_cache.rs
Expand Up @@ -7,6 +7,8 @@ use wasmer::{DeserializeError, Module, Store};
use crate::checksum::Checksum;
use crate::errors::{VmError, VmResult};

use crate::modules::current_wasmer_module_version;

/// Bump this version whenever the module system changes in a way
/// that old stored modules would be corrupt when loaded in the new system.
/// This needs to be done e.g. when switching between the jit/native engine.
Expand All @@ -32,16 +34,12 @@ use crate::errors::{VmError, VmResult};
/// Version for Wasmer 2.2.0 which contains a [module breaking change to 2.1.x](https://github.com/wasmerio/wasmer/pull/2747).
const MODULE_SERIALIZATION_VERSION: &str = "v3";

/// This header prefix contains the module type (wasmer-universal),
/// the magic value WASMER\0\0 and a little endian encoded uint32 version number.
/// The full header also contains a length that we do not check.
const EXPECTED_MODULE_HEADER_PREFIX: &[u8] = b"wasmer-universalWASMER\0\0\x01\0\0\0";

/// Representation of a directory that contains compiled Wasm artifacts.
pub struct FileSystemCache {
/// The base path this cache operates in. Within this path, versioned directories are created.
/// A sophisticated version of this cache might be able to read multiple input versions in the future.
base_path: PathBuf,
wasmer_module_version: u32,
}

impl FileSystemCache {
Expand All @@ -53,16 +51,17 @@ impl FileSystemCache {
/// This method is unsafe because there's no way to ensure the artifacts
/// stored in this cache haven't been corrupted or tampered with.
pub unsafe fn new(path: impl Into<PathBuf>) -> io::Result<Self> {
if !current_wasmer_module_header().starts_with(EXPECTED_MODULE_HEADER_PREFIX) {
panic!("Wasmer module format changed. Please update the expected version accordingly and bump MODULE_SERIALIZATION_VERSION.");
}
let wasmer_module_version = current_wasmer_module_version();

let path: PathBuf = path.into();
if path.exists() {
let metadata = path.metadata()?;
if metadata.is_dir() {
if !metadata.permissions().readonly() {
Ok(Self { base_path: path })
Ok(Self {
base_path: path,
wasmer_module_version,
})
} else {
// This directory is readonly.
Err(io::Error::new(
Expand All @@ -83,7 +82,10 @@ impl FileSystemCache {
} else {
// Create the directory and any parent directories if they don't yet exist.
fs::create_dir_all(&path)?;
Ok(Self { base_path: path })
Ok(Self {
base_path: path,
wasmer_module_version,
})
}
}

Expand Down Expand Up @@ -125,23 +127,14 @@ impl FileSystemCache {

/// The path to the latest version of the modules.
fn latest_modules_path(&self) -> PathBuf {
self.base_path.join(MODULE_SERIALIZATION_VERSION)
let version = format!(
"{}-wasmer{}",
MODULE_SERIALIZATION_VERSION, self.wasmer_module_version
);
self.base_path.join(version)
}
}

fn current_wasmer_module_header() -> Vec<u8> {
use crate::wasm_backend::compile;
// echo "(module)" > my.wat && wat2wasm my.wat && hexdump -C my.wasm
const WASM: &[u8] = b"\x00\x61\x73\x6d\x01\x00\x00\x00";
let module = compile(WASM, None, &[]).unwrap();
let mut bytes = module.serialize().unwrap_or_default();

const ENGINE_TYPE_LEN: usize = 16; // https://github.com/wasmerio/wasmer/blob/2.2.0-rc1/lib/engine-universal/src/artifact.rs#L48
const METADATA_HEADER_LEN: usize = 16; // https://github.com/wasmerio/wasmer/blob/2.2.0-rc1/lib/engine/src/artifact.rs#L251-L252
bytes.truncate(ENGINE_TYPE_LEN + METADATA_HEADER_LEN);
bytes
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -211,13 +204,11 @@ mod tests {
let module = compile(&wasm, None, &[]).unwrap();
cache.store(&checksum, &module).unwrap();

let file_path = format!("{}/v3/{}", tmp_dir.path().to_string_lossy(), checksum);
let file_path = format!(
"{}/v3-wasmer1/{}",
tmp_dir.path().to_string_lossy(),
checksum
);
let _serialized_module = fs::read(file_path).unwrap();
}

#[test]
fn current_wasmer_module_header_works() {
let header = current_wasmer_module_header();
assert!(header.starts_with(EXPECTED_MODULE_HEADER_PREFIX));
}
}
2 changes: 2 additions & 0 deletions packages/vm/src/modules/mod.rs
Expand Up @@ -2,7 +2,9 @@ mod file_system_cache;
mod in_memory_cache;
mod pinned_memory_cache;
mod sized_module;
mod versioning;

pub use file_system_cache::FileSystemCache;
pub use in_memory_cache::InMemoryCache;
pub use pinned_memory_cache::PinnedMemoryCache;
pub use versioning::current_wasmer_module_version;
56 changes: 56 additions & 0 deletions packages/vm/src/modules/versioning.rs
@@ -0,0 +1,56 @@
use std::convert::TryInto;

use crate::wasm_backend::compile;

/// This header prefix contains the module type (wasmer-universal) and
/// the magic value WASMER\0\0.
/// The full header also contains a little endian encoded uint32 version number
/// and a length that we do not check.
const EXPECTED_MODULE_HEADER_PREFIX: &[u8] = b"wasmer-universalWASMER\0\0\x01\0\0\0";

const ENGINE_TYPE_LEN: usize = 16; // https://github.com/wasmerio/wasmer/blob/2.2.0-rc1/lib/engine-universal/src/artifact.rs#L48
const METADATA_HEADER_LEN: usize = 16; // https://github.com/wasmerio/wasmer/blob/2.2.0-rc1/lib/engine/src/artifact.rs#L251-L252

fn current_wasmer_module_header() -> Vec<u8> {
// echo "(module)" > my.wat && wat2wasm my.wat && hexdump -C my.wasm
const WASM: &[u8] = b"\x00\x61\x73\x6d\x01\x00\x00\x00";
let module = compile(WASM, None, &[]).unwrap();
let mut bytes = module.serialize().unwrap_or_default();

bytes.truncate(ENGINE_TYPE_LEN + METADATA_HEADER_LEN);
bytes
}

/// Obtains the module version from Wasmer that is currently used.
/// As long as the overall format does not change, this returns a
/// counter (1 for Wasmer 2.2.0). When the format changes in an
/// unexpected way (e.g. a different engine is used or the meta
/// format changes), this panics. That way we can ensure an
/// incompatible module format can be found early in the development
/// cycle.
pub fn current_wasmer_module_version() -> u32 {
let header = current_wasmer_module_header();
if !header.starts_with(EXPECTED_MODULE_HEADER_PREFIX) {
panic!("Wasmer module format changed. Please update the expected version accordingly and bump MODULE_SERIALIZATION_VERSION.");
}

let metadata = &header[header.len() - METADATA_HEADER_LEN..];
u32::from_le_bytes((&metadata[8..12]).try_into().unwrap())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn current_wasmer_module_header_works() {
let header = current_wasmer_module_header();
assert!(header.starts_with(EXPECTED_MODULE_HEADER_PREFIX));
}

#[test]
fn current_wasmer_module_version_works() {
let version = current_wasmer_module_version();
assert_eq!(version, 1);
}
}

0 comments on commit 794b174

Please sign in to comment.