Simple module compilation cache (#203)
* Simple module compilation cache * Fix base64 encoding bug * Use warn! everywhere in cache system * Remove unused import * Temporary workaround for long path on Windows * Remove unused import for non-windows builds * Add command line argument to enable cache system + apply minor review feedback
This commit is contained in:
@@ -22,8 +22,8 @@ name = "wasm2obj"
|
||||
path = "src/wasm2obj.rs"
|
||||
|
||||
[dependencies]
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-native = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-native = "0.36.0"
|
||||
wasmtime-debug = { path = "wasmtime-debug" }
|
||||
wasmtime-environ = { path = "wasmtime-environ" }
|
||||
wasmtime-runtime = { path = "wasmtime-runtime" }
|
||||
@@ -34,8 +34,7 @@ wasmtime-wasi = { path = "wasmtime-wasi" }
|
||||
wasmtime-wasi-c = { path = "wasmtime-wasi-c", optional = true }
|
||||
wasi-common = { git = "https://github.com/CraneStation/wasi-common", rev = "c3994bf57b5d2f1f973b0e4e37bc385695aa4ed2"}
|
||||
docopt = "1.0.1"
|
||||
serde = "1.0.75"
|
||||
serde_derive = "1.0.75"
|
||||
serde = { "version" = "1.0.94", features = ["derive"] }
|
||||
faerie = "0.10.1"
|
||||
target-lexicon = { version = "0.4.0", default-features = false }
|
||||
pretty_env_logger = "0.3.0"
|
||||
|
||||
@@ -11,9 +11,9 @@ cargo-fuzz = true
|
||||
[dependencies]
|
||||
wasmtime-environ = { path = "../wasmtime-environ" }
|
||||
wasmtime-jit = { path = "../wasmtime-jit" }
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-native = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-native = "0.36.0"
|
||||
libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" }
|
||||
wasmparser = { version = "0.32.1", default-features = false }
|
||||
binaryen = "0.5.0"
|
||||
|
||||
@@ -29,14 +29,12 @@
|
||||
)
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use cranelift_codegen::isa;
|
||||
use cranelift_codegen::settings;
|
||||
use cranelift_native;
|
||||
use docopt::Docopt;
|
||||
use faerie::Artifact;
|
||||
use serde::Deserialize;
|
||||
use std::error::Error;
|
||||
use std::fmt::format;
|
||||
use std::fs::File;
|
||||
@@ -49,6 +47,7 @@ use std::str;
|
||||
use std::str::FromStr;
|
||||
use target_lexicon::Triple;
|
||||
use wasmtime_debug::{emit_debugsections, read_debuginfo};
|
||||
use wasmtime_environ::cache_conf;
|
||||
use wasmtime_environ::{Compiler, Cranelift, ModuleEnvironment, Tunables};
|
||||
use wasmtime_obj::emit_module;
|
||||
|
||||
@@ -63,7 +62,7 @@ The translation is dependent on the environment chosen.
|
||||
The default is a dummy environment that produces placeholder values.
|
||||
|
||||
Usage:
|
||||
wasm2obj [--target TARGET] [-g] <file> -o <output>
|
||||
wasm2obj [--target TARGET] [-cdg] <file> -o <output>
|
||||
wasm2obj --help | --version
|
||||
|
||||
Options:
|
||||
@@ -71,6 +70,7 @@ Options:
|
||||
-h, --help print this help message
|
||||
--target <TARGET> build for the target triple; default is the host machine
|
||||
-g generate debug information
|
||||
-c, --cache enable caching system
|
||||
--version print the Cranelift version
|
||||
-d, --debug enable debug output on stderr/stdout
|
||||
";
|
||||
@@ -82,6 +82,7 @@ struct Args {
|
||||
arg_target: Option<String>,
|
||||
flag_g: bool,
|
||||
flag_debug: bool,
|
||||
flag_cache: bool,
|
||||
}
|
||||
|
||||
fn read_wasm_file(path: PathBuf) -> Result<Vec<u8>, io::Error> {
|
||||
@@ -107,6 +108,8 @@ fn main() {
|
||||
utils::init_file_per_thread_logger();
|
||||
}
|
||||
|
||||
cache_conf::init(args.flag_cache);
|
||||
|
||||
let path = Path::new(&args.arg_file);
|
||||
match handle_module(
|
||||
path.to_path_buf(),
|
||||
|
||||
@@ -30,14 +30,12 @@
|
||||
)
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use cranelift_codegen::settings;
|
||||
use cranelift_codegen::settings::Configurable;
|
||||
use cranelift_native;
|
||||
use docopt::Docopt;
|
||||
use pretty_env_logger;
|
||||
use serde::Deserialize;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
@@ -48,6 +46,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::exit;
|
||||
use wabt;
|
||||
use wasi_common::preopen_dir;
|
||||
use wasmtime_environ::cache_conf;
|
||||
use wasmtime_jit::{ActionOutcome, Context};
|
||||
use wasmtime_wasi::instantiate_wasi;
|
||||
use wasmtime_wast::instantiate_spectest;
|
||||
@@ -67,13 +66,14 @@ including calling the start function if one is present. Additional functions
|
||||
given with --invoke are then called.
|
||||
|
||||
Usage:
|
||||
wasmtime [-odg] [--wasi-c] [--preload=<wasm>...] [--env=<env>...] [--dir=<dir>...] [--mapdir=<mapping>...] <file> [<arg>...]
|
||||
wasmtime [-odg] [--wasi-c] [--preload=<wasm>...] [--env=<env>...] [--dir=<dir>...] [--mapdir=<mapping>...] --invoke=<fn> <file> [<arg>...]
|
||||
wasmtime [-ocdg] [--wasi-c] [--preload=<wasm>...] [--env=<env>...] [--dir=<dir>...] [--mapdir=<mapping>...] <file> [<arg>...]
|
||||
wasmtime [-ocdg] [--wasi-c] [--preload=<wasm>...] [--env=<env>...] [--dir=<dir>...] [--mapdir=<mapping>...] --invoke=<fn> <file> [<arg>...]
|
||||
wasmtime --help | --version
|
||||
|
||||
Options:
|
||||
--invoke=<fn> name of function to run
|
||||
-o, --optimize runs optimization passes on the translated functions
|
||||
-c, --cache enable caching system
|
||||
-g generate debug information
|
||||
-d, --debug enable debug output on stderr/stdout
|
||||
--wasi-c enable the wasi-c implementation of WASI
|
||||
@@ -91,6 +91,7 @@ struct Args {
|
||||
arg_file: String,
|
||||
arg_arg: Vec<String>,
|
||||
flag_optimize: bool,
|
||||
flag_cache: bool,
|
||||
flag_debug: bool,
|
||||
flag_g: bool,
|
||||
flag_invoke: Option<String>,
|
||||
@@ -207,6 +208,8 @@ fn main() {
|
||||
utils::init_file_per_thread_logger();
|
||||
}
|
||||
|
||||
cache_conf::init(args.flag_cache);
|
||||
|
||||
let isa_builder = cranelift_native::builder().unwrap_or_else(|_| {
|
||||
panic!("host machine is not a supported target");
|
||||
});
|
||||
|
||||
11
src/wast.rs
11
src/wast.rs
@@ -25,16 +25,15 @@
|
||||
)
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use cranelift_codegen::settings;
|
||||
use cranelift_codegen::settings::Configurable;
|
||||
use cranelift_native;
|
||||
use docopt::Docopt;
|
||||
use pretty_env_logger;
|
||||
use serde::Deserialize;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use wasmtime_environ::cache_conf;
|
||||
use wasmtime_jit::Compiler;
|
||||
use wasmtime_wast::WastContext;
|
||||
|
||||
@@ -46,13 +45,14 @@ const USAGE: &str = "
|
||||
Wast test runner.
|
||||
|
||||
Usage:
|
||||
run_wast [-do] <file>...
|
||||
run_wast [-cdo] <file>...
|
||||
run_wast --help | --version
|
||||
|
||||
Options:
|
||||
-h, --help print this help message
|
||||
--version print the Cranelift version
|
||||
-o, --optimize runs optimization passes on the translated functions
|
||||
-c, --cache enable caching system
|
||||
-d, --debug enable debug output on stderr/stdout
|
||||
";
|
||||
|
||||
@@ -62,6 +62,7 @@ struct Args {
|
||||
flag_debug: bool,
|
||||
flag_function: Option<String>,
|
||||
flag_optimize: bool,
|
||||
flag_cache: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -80,6 +81,8 @@ fn main() {
|
||||
utils::init_file_per_thread_logger();
|
||||
}
|
||||
|
||||
cache_conf::init(args.flag_cache);
|
||||
|
||||
let isa_builder = cranelift_native::builder().unwrap_or_else(|_| {
|
||||
panic!("host machine is not a supported target");
|
||||
});
|
||||
|
||||
@@ -14,9 +14,9 @@ edition = "2018"
|
||||
[dependencies]
|
||||
gimli = "0.19.0"
|
||||
wasmparser = { version = "0.34.0" }
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-entity = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
faerie = "0.10.1"
|
||||
wasmtime-environ = { path = "../wasmtime-environ", default-features = false }
|
||||
target-lexicon = { version = "0.4.0", default-features = false }
|
||||
|
||||
@@ -12,14 +12,22 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-entity = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
lightbeam = { path = "../lightbeam", optional = true }
|
||||
failure = { version = "0.1.3", default-features = false }
|
||||
failure_derive = { version = "0.1.3", default-features = false }
|
||||
indexmap = "1.0.2"
|
||||
rayon = "1.1"
|
||||
directories = "2.0.1"
|
||||
sha2 = "0.8.0"
|
||||
base64 = "0.10.1"
|
||||
serde = { version = "1.0.94", features = ["derive"] }
|
||||
bincode = "1.1.4"
|
||||
lazy_static = "1.3.0"
|
||||
spin = "0.5.0"
|
||||
log = { version = "0.4.6", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::DefinedFuncIndex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::vec::Vec;
|
||||
|
||||
/// Single source location to generated address mapping.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct InstructionAddressMap {
|
||||
/// Original source location.
|
||||
pub srcloc: ir::SourceLoc,
|
||||
@@ -20,7 +21,7 @@ pub struct InstructionAddressMap {
|
||||
}
|
||||
|
||||
/// Function and its instructions addresses mappings.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FunctionAddressMap {
|
||||
/// Instructions maps.
|
||||
/// The array is sorted by the InstructionAddressMap::code_offset field.
|
||||
|
||||
342
wasmtime-environ/src/cache.rs
Normal file
342
wasmtime-environ/src/cache.rs
Normal file
@@ -0,0 +1,342 @@
|
||||
use crate::address_map::ModuleAddressMap;
|
||||
use crate::compilation::{CodeAndJTOffsets, Compilation, Relocations};
|
||||
use crate::module::Module;
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_codegen::isa;
|
||||
use directories::ProjectDirs;
|
||||
use lazy_static::lazy_static;
|
||||
use log::warn;
|
||||
use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
|
||||
use serde::ser::{self, Serialize, SerializeSeq, SerializeStruct, Serializer};
|
||||
#[cfg(windows)]
|
||||
use std::ffi::OsString;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
#[cfg(windows)]
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Module for configuring the cache system.
|
||||
pub mod conf {
|
||||
use spin::Once;
|
||||
|
||||
// Private static, so only internal function can access it.
|
||||
static CACHE_ENABLED: Once<bool> = Once::new();
|
||||
|
||||
/// Returns true if and only if the cache is enabled.
|
||||
pub fn cache_enabled() -> bool {
|
||||
// Not everyone knows about the cache system, i.e. the tests,
|
||||
// so the default is false.
|
||||
*CACHE_ENABLED.call_once(|| false)
|
||||
}
|
||||
|
||||
/// Initializes the cache system. Should be called exactly once,
|
||||
/// and before using the cache system. Otherwise it can panic.
|
||||
pub fn init(enabled: bool) {
|
||||
// init() should be called exactly once
|
||||
assert!(CACHE_ENABLED.r#try().is_none());
|
||||
let val = *CACHE_ENABLED.call_once(|| enabled);
|
||||
// But multiple threads can pass the first assertion, so let's guarantee consistency:
|
||||
assert!(val == enabled);
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CACHE_DIR: Option<PathBuf> =
|
||||
match ProjectDirs::from("", "CraneStation", "wasmtime") {
|
||||
Some(proj_dirs) => {
|
||||
let cache_dir = proj_dirs.cache_dir();
|
||||
// Temporary workaround for: https://github.com/rust-lang/rust/issues/32689
|
||||
#[cfg(windows)]
|
||||
let mut long_path = OsString::from("\\\\?\\");
|
||||
#[cfg(windows)]
|
||||
let cache_dir = {
|
||||
if cache_dir.starts_with("\\\\?\\") {
|
||||
cache_dir
|
||||
}
|
||||
else {
|
||||
long_path.push(cache_dir.as_os_str());
|
||||
Path::new(&long_path)
|
||||
}
|
||||
};
|
||||
match fs::create_dir_all(cache_dir) {
|
||||
Ok(()) => (),
|
||||
Err(err) => warn!("Unable to create cache directory, failed with: {}", err),
|
||||
};
|
||||
Some(cache_dir.to_path_buf())
|
||||
}
|
||||
None => {
|
||||
warn!("Unable to find cache directory");
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct ModuleCacheEntry {
|
||||
mod_cache_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct ModuleCacheData {
|
||||
compilation: Compilation,
|
||||
relocations: Relocations,
|
||||
address_transforms: ModuleAddressMap,
|
||||
}
|
||||
|
||||
type ModuleCacheDataTupleType = (Compilation, Relocations, ModuleAddressMap);
|
||||
|
||||
impl ModuleCacheEntry {
|
||||
pub fn new(module: &Module, _isa: &dyn isa::TargetIsa, _generate_debug_info: bool) -> Self {
|
||||
// TODO: cache directory hierarchy with isa name, compiler name & git revision, and files with flag if debug symbols are available
|
||||
let mod_cache_path = if conf::cache_enabled() {
|
||||
CACHE_DIR.clone().and_then(|p| {
|
||||
module.hash.map(|hash| {
|
||||
p.join(format!(
|
||||
"mod-{}",
|
||||
base64::encode_config(&hash, base64::URL_SAFE_NO_PAD) // standard encoding uses '/' which can't be used for filename
|
||||
))
|
||||
})
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ModuleCacheEntry { mod_cache_path }
|
||||
}
|
||||
|
||||
pub fn get_data(&self) -> Option<ModuleCacheData> {
|
||||
if let Some(p) = &self.mod_cache_path {
|
||||
match fs::read(p) {
|
||||
Ok(cache_bytes) => match bincode::deserialize(&cache_bytes[..]) {
|
||||
Ok(data) => Some(data),
|
||||
Err(err) => {
|
||||
warn!("Failed to deserialize cached code: {}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_data(&self, data: &ModuleCacheData) {
|
||||
if let Some(p) = &self.mod_cache_path {
|
||||
let cache_buf = match bincode::serialize(&data) {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
warn!("Failed to serialize cached code: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match fs::write(p, &cache_buf) {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Failed to write cached code to disk, path: {}, message: {}",
|
||||
p.display(),
|
||||
err
|
||||
);
|
||||
match fs::remove_file(p) {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
if err.kind() != io::ErrorKind::NotFound {
|
||||
warn!(
|
||||
"Failed to cleanup invalid cache, path: {}, message: {}",
|
||||
p.display(),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleCacheData {
|
||||
pub fn from_tuple(data: ModuleCacheDataTupleType) -> Self {
|
||||
Self {
|
||||
compilation: data.0,
|
||||
relocations: data.1,
|
||||
address_transforms: data.2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_tuple(self) -> ModuleCacheDataTupleType {
|
||||
(self.compilation, self.relocations, self.address_transforms)
|
||||
}
|
||||
}
|
||||
|
||||
//-////////////////////////////////////////////////////////////////////
|
||||
// Serialization and deserialization of type containing SecondaryMap //
|
||||
//-////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum JtOffsetsWrapper<'a> {
|
||||
Ref(&'a ir::JumpTableOffsets), // for serialization
|
||||
Data(ir::JumpTableOffsets), // for deserialization
|
||||
}
|
||||
|
||||
impl Serialize for CodeAndJTOffsets {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut cajto = serializer.serialize_struct("CodeAndJTOffsets", 2)?;
|
||||
cajto.serialize_field("body", &self.body)?;
|
||||
cajto.serialize_field("jt_offsets", &JtOffsetsWrapper::Ref(&self.jt_offsets))?;
|
||||
cajto.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for CodeAndJTOffsets {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "lowercase")]
|
||||
enum Field {
|
||||
Body,
|
||||
JtOffsets,
|
||||
};
|
||||
|
||||
struct CodeAndJTOffsetsVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for CodeAndJTOffsetsVisitor {
|
||||
type Value = CodeAndJTOffsets;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("struct CodeAndJTOffsets")
|
||||
}
|
||||
|
||||
fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: SeqAccess<'de>,
|
||||
{
|
||||
let body = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
||||
let jt_offsets = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
|
||||
match jt_offsets {
|
||||
JtOffsetsWrapper::Data(jt_offsets) => Ok(CodeAndJTOffsets { body, jt_offsets }),
|
||||
JtOffsetsWrapper::Ref(_) => Err(de::Error::custom(
|
||||
"Received invalid variant of JtOffsetsWrapper",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut body = None;
|
||||
let mut jt_offsets = None;
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::Body => {
|
||||
if body.is_some() {
|
||||
return Err(de::Error::duplicate_field("body"));
|
||||
}
|
||||
body = Some(map.next_value()?);
|
||||
}
|
||||
Field::JtOffsets => {
|
||||
if jt_offsets.is_some() {
|
||||
return Err(de::Error::duplicate_field("jt_offsets"));
|
||||
}
|
||||
jt_offsets = Some(map.next_value()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let body = body.ok_or_else(|| de::Error::missing_field("body"))?;
|
||||
let jt_offsets =
|
||||
jt_offsets.ok_or_else(|| de::Error::missing_field("jt_offsets"))?;
|
||||
match jt_offsets {
|
||||
JtOffsetsWrapper::Data(jt_offsets) => Ok(CodeAndJTOffsets { body, jt_offsets }),
|
||||
JtOffsetsWrapper::Ref(_) => Err(de::Error::custom(
|
||||
"Received invalid variant of JtOffsetsWrapper",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FIELDS: &'static [&'static str] = &["body", "jt_offsets"];
|
||||
deserializer.deserialize_struct("CodeAndJTOffsets", FIELDS, CodeAndJTOffsetsVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for JtOffsetsWrapper<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
JtOffsetsWrapper::Ref(data) => {
|
||||
// TODO: bincode encodes option as "byte for Some/None" and then optionally the content
|
||||
// TODO: we can actually optimize it by encoding manually bitmask, then elements
|
||||
let default_val = data.get_default();
|
||||
let mut seq = serializer.serialize_seq(Some(1 + data.len()))?;
|
||||
seq.serialize_element(&Some(default_val))?;
|
||||
for e in data.values() {
|
||||
let some_e = Some(e);
|
||||
seq.serialize_element(if e == default_val { &None } else { &some_e })?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
JtOffsetsWrapper::Data(_) => Err(ser::Error::custom(
|
||||
"Received invalid variant of JtOffsetsWrapper",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for JtOffsetsWrapper<'_> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct JtOffsetsWrapperVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for JtOffsetsWrapperVisitor {
|
||||
type Value = JtOffsetsWrapper<'static>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("struct JtOffsetsWrapper")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
match seq.next_element()? {
|
||||
Some(Some(default_val)) => {
|
||||
let mut m = cranelift_entity::SecondaryMap::with_default(default_val);
|
||||
let mut idx = 0;
|
||||
while let Some(val) = seq.next_element()? {
|
||||
let val: Option<_> = val; // compiler can't infer the type, and this line is needed
|
||||
match ir::JumpTable::with_number(idx) {
|
||||
Some(jt_idx) => m[jt_idx] = val.unwrap_or(default_val),
|
||||
None => {
|
||||
return Err(serde::de::Error::custom(
|
||||
"Invalid JumpTable reference",
|
||||
))
|
||||
}
|
||||
};
|
||||
idx += 1;
|
||||
}
|
||||
Ok(JtOffsetsWrapper::Data(m))
|
||||
}
|
||||
_ => Err(serde::de::Error::custom("Default value required")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(JtOffsetsWrapperVisitor {})
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use crate::module_environ::FunctionBodyData;
|
||||
use cranelift_codegen::{binemit, ir, isa, CodegenError};
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Range;
|
||||
use std::vec::Vec;
|
||||
|
||||
@@ -23,7 +24,7 @@ pub struct CodeAndJTOffsets {
|
||||
type Functions = PrimaryMap<DefinedFuncIndex, CodeAndJTOffsets>;
|
||||
|
||||
/// The result of compiling a WebAssembly module's functions.
|
||||
#[derive(Debug)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct Compilation {
|
||||
/// Compiled machine code for the function bodies.
|
||||
functions: Functions,
|
||||
@@ -94,7 +95,7 @@ impl<'a> Iterator for Iter<'a> {
|
||||
}
|
||||
|
||||
/// A record of a relocation to perform.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Relocation {
|
||||
/// The relocation code.
|
||||
pub reloc: binemit::Reloc,
|
||||
@@ -107,7 +108,7 @@ pub struct Relocation {
|
||||
}
|
||||
|
||||
/// Destination function. Can be either user function or some special one, like `memory.grow`.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||
pub enum RelocationTarget {
|
||||
/// The user function index.
|
||||
UserFunc(FuncIndex),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Support for compiling with Cranelift.
|
||||
|
||||
use crate::address_map::{FunctionAddressMap, InstructionAddressMap, ModuleAddressMap};
|
||||
use crate::cache::{ModuleCacheData, ModuleCacheEntry};
|
||||
use crate::compilation::{
|
||||
CodeAndJTOffsets, Compilation, CompileError, Relocation, RelocationTarget, Relocations,
|
||||
};
|
||||
@@ -123,73 +124,90 @@ impl crate::compilation::Compiler for Cranelift {
|
||||
isa: &dyn isa::TargetIsa,
|
||||
generate_debug_info: bool,
|
||||
) -> Result<(Compilation, Relocations, ModuleAddressMap), CompileError> {
|
||||
let mut functions = PrimaryMap::with_capacity(function_body_inputs.len());
|
||||
let mut relocations = PrimaryMap::with_capacity(function_body_inputs.len());
|
||||
let mut address_transforms = PrimaryMap::with_capacity(function_body_inputs.len());
|
||||
let cache_entry = ModuleCacheEntry::new(module, isa, generate_debug_info);
|
||||
|
||||
function_body_inputs
|
||||
.into_iter()
|
||||
.collect::<Vec<(DefinedFuncIndex, &FunctionBodyData<'data>)>>()
|
||||
.par_iter()
|
||||
.map(|(i, input)| {
|
||||
let func_index = module.func_index(*i);
|
||||
let mut context = Context::new();
|
||||
context.func.name = get_func_name(func_index);
|
||||
context.func.signature = module.signatures[module.functions[func_index]].clone();
|
||||
let data = match cache_entry.get_data() {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
let mut functions = PrimaryMap::with_capacity(function_body_inputs.len());
|
||||
let mut relocations = PrimaryMap::with_capacity(function_body_inputs.len());
|
||||
let mut address_transforms = PrimaryMap::with_capacity(function_body_inputs.len());
|
||||
|
||||
let mut trans = FuncTranslator::new();
|
||||
trans
|
||||
.translate(
|
||||
input.data,
|
||||
input.module_offset,
|
||||
&mut context.func,
|
||||
&mut FuncEnvironment::new(isa.frontend_config(), module),
|
||||
)
|
||||
.map_err(CompileError::Wasm)?;
|
||||
function_body_inputs
|
||||
.into_iter()
|
||||
.collect::<Vec<(DefinedFuncIndex, &FunctionBodyData<'data>)>>()
|
||||
.par_iter()
|
||||
.map(|(i, input)| {
|
||||
let func_index = module.func_index(*i);
|
||||
let mut context = Context::new();
|
||||
context.func.name = get_func_name(func_index);
|
||||
context.func.signature =
|
||||
module.signatures[module.functions[func_index]].clone();
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut reloc_sink = RelocSink::new(func_index);
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
context
|
||||
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||
.map_err(CompileError::Codegen)?;
|
||||
let mut trans = FuncTranslator::new();
|
||||
trans
|
||||
.translate(
|
||||
input.data,
|
||||
input.module_offset,
|
||||
&mut context.func,
|
||||
&mut FuncEnvironment::new(isa.frontend_config(), module),
|
||||
)
|
||||
.map_err(CompileError::Wasm)?;
|
||||
|
||||
let jt_offsets = context.func.jt_offsets.clone();
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut reloc_sink = RelocSink::new(func_index);
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
context
|
||||
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||
.map_err(CompileError::Codegen)?;
|
||||
|
||||
let address_transform = if generate_debug_info {
|
||||
let body_len = code_buf.len();
|
||||
let at = get_address_transform(&context, isa);
|
||||
let jt_offsets = context.func.jt_offsets.clone();
|
||||
|
||||
Some(FunctionAddressMap {
|
||||
instructions: at,
|
||||
body_offset: 0,
|
||||
body_len,
|
||||
let address_transform = if generate_debug_info {
|
||||
let body_len = code_buf.len();
|
||||
let at = get_address_transform(&context, isa);
|
||||
|
||||
Some(FunctionAddressMap {
|
||||
instructions: at,
|
||||
body_offset: 0,
|
||||
body_len,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((
|
||||
code_buf,
|
||||
jt_offsets,
|
||||
reloc_sink.func_relocs,
|
||||
address_transform,
|
||||
))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
.collect::<Result<Vec<_>, CompileError>>()?
|
||||
.into_iter()
|
||||
.for_each(|(function, func_jt_offsets, relocs, address_transform)| {
|
||||
functions.push(CodeAndJTOffsets {
|
||||
body: function,
|
||||
jt_offsets: func_jt_offsets,
|
||||
});
|
||||
relocations.push(relocs);
|
||||
if let Some(address_transform) = address_transform {
|
||||
address_transforms.push(address_transform);
|
||||
}
|
||||
});
|
||||
|
||||
Ok((
|
||||
code_buf,
|
||||
jt_offsets,
|
||||
reloc_sink.func_relocs,
|
||||
address_transform,
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>, CompileError>>()?
|
||||
.into_iter()
|
||||
.for_each(|(function, func_jt_offsets, relocs, address_transform)| {
|
||||
functions.push(CodeAndJTOffsets {
|
||||
body: function,
|
||||
jt_offsets: func_jt_offsets,
|
||||
});
|
||||
relocations.push(relocs);
|
||||
if let Some(address_transform) = address_transform {
|
||||
address_transforms.push(address_transform);
|
||||
}
|
||||
});
|
||||
// TODO: Reorganize where we create the Vec for the resolved imports.
|
||||
|
||||
// TODO: Reorganize where we create the Vec for the resolved imports.
|
||||
Ok((Compilation::new(functions), relocations, address_transforms))
|
||||
let data = ModuleCacheData::from_tuple((
|
||||
Compilation::new(functions),
|
||||
relocations,
|
||||
address_transforms,
|
||||
));
|
||||
cache_entry.update_data(&data);
|
||||
data
|
||||
}
|
||||
};
|
||||
|
||||
Ok(data.to_tuple())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +45,14 @@ mod module_environ;
|
||||
mod tunables;
|
||||
mod vmoffsets;
|
||||
|
||||
mod cache;
|
||||
|
||||
pub mod cranelift;
|
||||
#[cfg(feature = "lightbeam")]
|
||||
pub mod lightbeam;
|
||||
|
||||
pub use crate::address_map::{FunctionAddressMap, InstructionAddressMap, ModuleAddressMap};
|
||||
pub use crate::cache::conf as cache_conf;
|
||||
pub use crate::compilation::{
|
||||
Compilation, CompileError, Compiler, Relocation, RelocationTarget, Relocations,
|
||||
};
|
||||
|
||||
@@ -170,6 +170,10 @@ pub struct Module {
|
||||
|
||||
/// WebAssembly table initializers.
|
||||
pub table_elements: Vec<TableElements>,
|
||||
|
||||
/// Hash of the source wasm code if this module is not synthesized.
|
||||
/// TODO: this is temporary workaround. Will be replaced with derive macro.
|
||||
pub hash: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
@@ -188,6 +192,7 @@ impl Module {
|
||||
exports: IndexMap::new(),
|
||||
start_func: None,
|
||||
table_elements: Vec::new(),
|
||||
hash: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use cranelift_wasm::{
|
||||
self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex,
|
||||
SignatureIndex, Table, TableIndex, WasmResult,
|
||||
};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::boxed::Box;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
@@ -79,6 +80,11 @@ impl<'data> ModuleEnvironment<'data> {
|
||||
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<ModuleTranslation<'data>> {
|
||||
translate_module(data, &mut self)?;
|
||||
|
||||
// TODO: this is temporary workaround and will be replaced with derive macro.
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(data);
|
||||
self.result.module.hash = Some(hasher.result().into());
|
||||
|
||||
Ok(self.result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-entity = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-frontend = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-frontend = "0.36.0"
|
||||
wasmtime-environ = { path = "../wasmtime-environ", default-features = false }
|
||||
wasmtime-runtime = { path = "../wasmtime-runtime", default-features = false }
|
||||
wasmtime-debug = { path = "../wasmtime-debug", default-features = false }
|
||||
|
||||
@@ -12,8 +12,8 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-entity = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
wasmtime-environ = { path = "../wasmtime-environ" }
|
||||
faerie = "0.10.1"
|
||||
|
||||
@@ -12,9 +12,9 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-entity = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
wasmtime-environ = { path = "../wasmtime-environ", default-features = false }
|
||||
region = "2.0.0"
|
||||
lazy_static = "1.2.0"
|
||||
|
||||
@@ -13,9 +13,9 @@ readme = "README.md"
|
||||
wasmtime-runtime = { path = "../wasmtime-runtime" }
|
||||
wasmtime-environ = { path = "../wasmtime-environ" }
|
||||
wasmtime-jit = { path = "../wasmtime-jit" }
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-entity = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
target-lexicon = "0.4.0"
|
||||
log = { version = "0.4.6", default-features = false }
|
||||
libc = "0.2.50"
|
||||
|
||||
@@ -14,9 +14,9 @@ wasmtime-runtime = { path = "../wasmtime-runtime" }
|
||||
wasmtime-environ = { path = "../wasmtime-environ" }
|
||||
wasmtime-jit = { path = "../wasmtime-jit" }
|
||||
wasi-common = { git = "https://github.com/CraneStation/wasi-common", rev = "c3994bf57b5d2f1f973b0e4e37bc385695aa4ed2"}
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-entity = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
target-lexicon = "0.4.0"
|
||||
log = { version = "0.4.6", default-features = false }
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
cranelift-codegen = "0.33.0"
|
||||
cranelift-native = "0.33.0"
|
||||
cranelift-wasm = "0.33.0"
|
||||
cranelift-entity = "0.33.0"
|
||||
cranelift-codegen = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-native = "0.36.0"
|
||||
cranelift-wasm = { version = "0.36.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.36.0", features = ["enable-serde"] }
|
||||
wasmtime-jit = { path = "../wasmtime-jit" }
|
||||
wasmtime-runtime = { path = "../wasmtime-runtime" }
|
||||
wasmtime-environ = { path = "../wasmtime-environ" }
|
||||
|
||||
Reference in New Issue
Block a user