Validate modules while translating (#2059)
* Validate modules while translating This commit is a change to cranelift-wasm to validate each function body as it is translated. Additionally top-level module translation functions will perform module validation. This commit builds on changes in wasmparser to perform module validation interwtwined with parsing and translation. This will be necessary for future wasm features such as module linking where the type behind a function index, for example, can be far away in another module. Additionally this also brings a nice benefit where parsing the binary only happens once (instead of having an up-front serial validation step) and validation can happen in parallel for each function. Most of the changes in this commit are plumbing to make sure everything lines up right. The major functional change here is that module compilation should be faster by validating in parallel (or skipping function validation entirely in the case of a cache hit). Otherwise from a user-facing perspective nothing should be that different. This commit does mean that cranelift's translation now inherently validates the input wasm module. This means that the Spidermonkey integration of cranelift-wasm will also be validating the function as it's being translated with cranelift. The associated PR for wasmparser (bytecodealliance/wasmparser#62) provides the necessary tools to create a `FuncValidator` for Gecko, but this is something I'll want careful review for before landing! * Read function operators until EOF This way we can let the validator take care of any issues with mismatched `end` instructions and/or trailing operators/bytes.
This commit is contained in:
@@ -267,8 +267,11 @@ fn get_function_address_map<'data>(
|
||||
// Generate artificial srcloc for function start/end to identify boundary
|
||||
// within module. Similar to FuncTranslator::cur_srcloc(): it will wrap around
|
||||
// if byte code is larger than 4 GB.
|
||||
let start_srcloc = ir::SourceLoc::new(data.module_offset as u32);
|
||||
let end_srcloc = ir::SourceLoc::new((data.module_offset + data.data.len()) as u32);
|
||||
let data = data.body.get_binary_reader();
|
||||
let offset = data.original_position();
|
||||
let len = data.bytes_remaining();
|
||||
let start_srcloc = ir::SourceLoc::new(offset as u32);
|
||||
let end_srcloc = ir::SourceLoc::new((offset + len) as u32);
|
||||
|
||||
FunctionAddressMap {
|
||||
instructions,
|
||||
@@ -302,7 +305,7 @@ impl Compiler for Cranelift {
|
||||
&self,
|
||||
translation: &ModuleTranslation<'_>,
|
||||
func_index: DefinedFuncIndex,
|
||||
input: &FunctionBodyData<'_>,
|
||||
mut input: FunctionBodyData<'_>,
|
||||
isa: &dyn isa::TargetIsa,
|
||||
) -> Result<CompiledFunction, CompileError> {
|
||||
let module = &translation.module;
|
||||
@@ -351,14 +354,15 @@ impl Compiler for Cranelift {
|
||||
});
|
||||
context.func.stack_limit = Some(stack_limit);
|
||||
let mut func_translator = self.take_translator();
|
||||
let result = func_translator.translate(
|
||||
translation.module_translation.as_ref().unwrap(),
|
||||
input.data,
|
||||
input.module_offset,
|
||||
let result = func_translator.translate_body(
|
||||
&mut input.validator,
|
||||
input.body.clone(),
|
||||
&mut context.func,
|
||||
&mut func_env,
|
||||
);
|
||||
self.save_translator(func_translator);
|
||||
if result.is_ok() {
|
||||
self.save_translator(func_translator);
|
||||
}
|
||||
result?;
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
@@ -381,7 +385,7 @@ impl Compiler for Cranelift {
|
||||
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
|
||||
})?;
|
||||
|
||||
let address_transform = get_function_address_map(&context, input, code_buf.len(), isa);
|
||||
let address_transform = get_function_address_map(&context, &input, code_buf.len(), isa);
|
||||
|
||||
let ranges = if tunables.debug_info {
|
||||
let ranges = context.build_value_labels_ranges(isa).map_err(|error| {
|
||||
|
||||
@@ -13,7 +13,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
gimli = "0.22.0"
|
||||
wasmparser = "0.59.0"
|
||||
wasmparser = "0.62.0"
|
||||
object = { version = "0.21.1", default-features = false, features = ["read", "write"] }
|
||||
wasmtime-environ = { path = "../environ", version = "0.20.0" }
|
||||
target-lexicon = { version = "0.11.0", default-features = false }
|
||||
|
||||
@@ -16,7 +16,7 @@ anyhow = "1.0"
|
||||
cranelift-codegen = { path = "../../cranelift/codegen", version = "0.67.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { path = "../../cranelift/entity", version = "0.67.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { path = "../../cranelift/wasm", version = "0.67.0", features = ["enable-serde"] }
|
||||
wasmparser = "0.59.0"
|
||||
wasmparser = "0.62.0"
|
||||
indexmap = { version = "1.0.2", features = ["serde-1"] }
|
||||
thiserror = "1.0.4"
|
||||
serde = { version = "1.0.94", features = ["derive"] }
|
||||
|
||||
@@ -103,7 +103,7 @@ pub trait Compiler: Send + Sync {
|
||||
&self,
|
||||
translation: &ModuleTranslation<'_>,
|
||||
index: DefinedFuncIndex,
|
||||
data: &FunctionBodyData<'_>,
|
||||
data: FunctionBodyData<'_>,
|
||||
isa: &dyn isa::TargetIsa,
|
||||
) -> Result<CompiledFunction, CompileError>;
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@ use std::convert::TryFrom;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use wasmparser::Type as WasmType;
|
||||
use wasmparser::{FuncValidator, FunctionBody, ValidatorResources, WasmFeatures};
|
||||
|
||||
/// Object containing the standalone environment information.
|
||||
pub struct ModuleEnvironment<'data> {
|
||||
/// The result to be filled in.
|
||||
result: ModuleTranslation<'data>,
|
||||
code_index: u32,
|
||||
features: WasmFeatures,
|
||||
}
|
||||
|
||||
/// The result of translating via `ModuleEnvironment`. Function bodies are not
|
||||
@@ -50,13 +52,11 @@ pub struct ModuleTranslation<'data> {
|
||||
}
|
||||
|
||||
/// Contains function data: byte code and its offset in the module.
|
||||
#[derive(Hash)]
|
||||
pub struct FunctionBodyData<'a> {
|
||||
/// Body byte code.
|
||||
pub data: &'a [u8],
|
||||
|
||||
/// Body offset in the module file.
|
||||
pub module_offset: usize,
|
||||
/// The body of the function, containing code and locals.
|
||||
pub body: FunctionBody<'a>,
|
||||
/// Validator for the function body
|
||||
pub validator: FuncValidator<ValidatorResources>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -102,7 +102,11 @@ pub struct FunctionMetadata {
|
||||
|
||||
impl<'data> ModuleEnvironment<'data> {
|
||||
/// Allocates the environment data structures.
|
||||
pub fn new(target_config: TargetFrontendConfig, tunables: &Tunables) -> Self {
|
||||
pub fn new(
|
||||
target_config: TargetFrontendConfig,
|
||||
tunables: &Tunables,
|
||||
features: &WasmFeatures,
|
||||
) -> Self {
|
||||
Self {
|
||||
result: ModuleTranslation {
|
||||
target_config,
|
||||
@@ -118,6 +122,7 @@ impl<'data> ModuleEnvironment<'data> {
|
||||
},
|
||||
},
|
||||
code_index: 0,
|
||||
features: *features,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,21 +447,15 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
|
||||
fn define_function_body(
|
||||
&mut self,
|
||||
_module_translation: &ModuleTranslationState,
|
||||
body_bytes: &'data [u8],
|
||||
body_offset: usize,
|
||||
validator: FuncValidator<ValidatorResources>,
|
||||
body: FunctionBody<'data>,
|
||||
) -> WasmResult<()> {
|
||||
self.result.function_body_inputs.push(FunctionBodyData {
|
||||
data: body_bytes,
|
||||
module_offset: body_offset,
|
||||
});
|
||||
if let Some(info) = &mut self.result.debuginfo {
|
||||
let func_index = self.code_index + self.result.module.num_imported_funcs as u32;
|
||||
let func_index = FuncIndex::from_u32(func_index);
|
||||
let sig_index = self.result.module.functions[func_index];
|
||||
let sig = &self.result.module.signatures[sig_index];
|
||||
let mut locals = Vec::new();
|
||||
let body = wasmparser::FunctionBody::new(body_offset, body_bytes);
|
||||
for pair in body.get_locals_reader()? {
|
||||
locals.push(pair?);
|
||||
}
|
||||
@@ -465,6 +464,9 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
params: sig.0.params.iter().cloned().map(|i| i.into()).collect(),
|
||||
});
|
||||
}
|
||||
self.result
|
||||
.function_body_inputs
|
||||
.push(FunctionBodyData { validator, body });
|
||||
self.code_index += 1;
|
||||
Ok(())
|
||||
}
|
||||
@@ -564,6 +566,10 @@ and for re-adding support for interface types you can see this issue:
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn wasm_features(&self) -> WasmFeatures {
|
||||
self.features
|
||||
}
|
||||
}
|
||||
|
||||
/// Add environment-specific function parameters.
|
||||
|
||||
@@ -13,8 +13,8 @@ binaryen = { version = "0.10.0", optional = true }
|
||||
env_logger = "0.7.1"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.1"
|
||||
wasmparser = "0.59.0"
|
||||
wasmprinter = "0.2.6"
|
||||
wasmparser = "0.62.0"
|
||||
wasmprinter = "0.2.8"
|
||||
wasmtime = { path = "../wasmtime" }
|
||||
wasmtime-wast = { path = "../wast" }
|
||||
|
||||
|
||||
@@ -219,7 +219,10 @@ fn predict_rss(wasm: &[u8]) -> Result<usize> {
|
||||
// the minimum amount of memory to our predicted rss.
|
||||
Payload::MemorySection(s) => {
|
||||
for entry in s {
|
||||
let initial = entry?.limits.initial as usize;
|
||||
let initial = match entry? {
|
||||
MemoryType::M32 { limits, .. } => limits.initial as usize,
|
||||
MemoryType::M64 { limits } => limits.initial as usize,
|
||||
};
|
||||
prediction += initial * 64 * 1024;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ rayon = { version = "1.0", optional = true }
|
||||
region = "2.1.0"
|
||||
thiserror = "1.0.4"
|
||||
target-lexicon = { version = "0.11.0", default-features = false }
|
||||
wasmparser = "0.59.0"
|
||||
wasmparser = "0.62.0"
|
||||
more-asserts = "0.2.1"
|
||||
anyhow = "1.0"
|
||||
cfg-if = "0.1.9"
|
||||
|
||||
@@ -4,6 +4,8 @@ use crate::instantiate::SetupError;
|
||||
use crate::object::{build_object, ObjectUnwindInfo};
|
||||
use object::write::Object;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
use wasmparser::WasmFeatures;
|
||||
use wasmtime_debug::{emit_dwarf, DwarfSection};
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
|
||||
@@ -40,11 +42,17 @@ pub struct Compiler {
|
||||
compiler: Box<dyn EnvCompiler>,
|
||||
strategy: CompilationStrategy,
|
||||
tunables: Tunables,
|
||||
features: WasmFeatures,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
/// Construct a new `Compiler`.
|
||||
pub fn new(isa: Box<dyn TargetIsa>, strategy: CompilationStrategy, tunables: Tunables) -> Self {
|
||||
pub fn new(
|
||||
isa: Box<dyn TargetIsa>,
|
||||
strategy: CompilationStrategy,
|
||||
tunables: Tunables,
|
||||
features: WasmFeatures,
|
||||
) -> Self {
|
||||
Self {
|
||||
isa,
|
||||
strategy,
|
||||
@@ -56,6 +64,7 @@ impl Compiler {
|
||||
CompilationStrategy::Lightbeam => Box::new(wasmtime_lightbeam::Lightbeam),
|
||||
},
|
||||
tunables,
|
||||
features,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,20 +116,26 @@ impl Compiler {
|
||||
&self.tunables
|
||||
}
|
||||
|
||||
/// Return the enabled wasm features.
|
||||
pub fn features(&self) -> &WasmFeatures {
|
||||
&self.features
|
||||
}
|
||||
|
||||
/// Compile the given function bodies.
|
||||
pub fn compile<'data>(
|
||||
&self,
|
||||
translation: &ModuleTranslation,
|
||||
translation: &mut ModuleTranslation,
|
||||
) -> Result<Compilation, SetupError> {
|
||||
let functions = mem::take(&mut translation.function_body_inputs);
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "parallel-compilation")] {
|
||||
use rayon::prelude::*;
|
||||
let iter = translation.function_body_inputs
|
||||
.iter()
|
||||
let iter = functions
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
.into_par_iter();
|
||||
} else {
|
||||
let iter = translation.function_body_inputs.iter();
|
||||
let iter = functions.into_iter();
|
||||
}
|
||||
}
|
||||
let funcs = iter
|
||||
@@ -161,12 +176,14 @@ impl Hash for Compiler {
|
||||
compiler: _,
|
||||
isa,
|
||||
tunables,
|
||||
features,
|
||||
} = self;
|
||||
|
||||
// Hash compiler's flags: compilation strategy, isa, frontend config,
|
||||
// misc tunables.
|
||||
strategy.hash(hasher);
|
||||
isa.triple().hash(hasher);
|
||||
features.hash(hasher);
|
||||
// TODO: if this `to_string()` is too expensive then we should upstream
|
||||
// a native hashing ability of flags into cranelift itself, but
|
||||
// compilation and/or cache loading is relatively expensive so seems
|
||||
|
||||
@@ -82,9 +82,13 @@ struct FunctionInfo {
|
||||
impl CompilationArtifacts {
|
||||
/// 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(),
|
||||
compiler.features(),
|
||||
);
|
||||
|
||||
let translation = environ
|
||||
let mut translation = environ
|
||||
.translate(data)
|
||||
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
|
||||
|
||||
@@ -92,7 +96,7 @@ impl CompilationArtifacts {
|
||||
obj,
|
||||
unwind_info,
|
||||
funcs,
|
||||
} = compiler.compile(&translation)?;
|
||||
} = compiler.compile(&mut translation)?;
|
||||
|
||||
let ModuleTranslation {
|
||||
module,
|
||||
|
||||
@@ -24,7 +24,7 @@ smallvec = "1.0.0"
|
||||
staticvec = "0.10"
|
||||
thiserror = "1.0.9"
|
||||
typemap = "0.3"
|
||||
wasmparser = "0.59.0"
|
||||
wasmparser = "0.62.0"
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.2"
|
||||
|
||||
@@ -437,8 +437,9 @@ pub struct MemoryImmediate {
|
||||
|
||||
impl From<WasmMemoryImmediate> for MemoryImmediate {
|
||||
fn from(other: WasmMemoryImmediate) -> Self {
|
||||
assert_eq!(other.memory, 0);
|
||||
MemoryImmediate {
|
||||
flags: other.flags,
|
||||
flags: other.align.into(),
|
||||
offset: other.offset,
|
||||
}
|
||||
}
|
||||
@@ -544,12 +545,8 @@ pub enum Operator<Label> {
|
||||
Store32 {
|
||||
memarg: MemoryImmediate,
|
||||
},
|
||||
MemorySize {
|
||||
reserved: u32,
|
||||
},
|
||||
MemoryGrow {
|
||||
reserved: u32,
|
||||
},
|
||||
MemorySize,
|
||||
MemoryGrow,
|
||||
Const(Value),
|
||||
Eq(SignlessType),
|
||||
Ne(SignlessType),
|
||||
@@ -1940,12 +1937,13 @@ where
|
||||
}
|
||||
WasmOperator::BrTable { table } => {
|
||||
self.unreachable = true;
|
||||
let (targets, default) = table.read_table()?;
|
||||
let mut targets = table.targets().collect::<Result<Vec<_>, _>>()?;
|
||||
let default = targets.pop().unwrap().0;
|
||||
let control_frames = &mut self.control_frames;
|
||||
let stack = &self.stack;
|
||||
let targets = targets
|
||||
.into_iter()
|
||||
.map(|&depth| {
|
||||
.map(|(depth, _)| {
|
||||
control_frames[depth as _].mark_branched_to();
|
||||
let block = &control_frames[depth as _];
|
||||
|
||||
@@ -2121,8 +2119,8 @@ where
|
||||
WasmOperator::I64Store32 { memarg } => one(Operator::Store32 {
|
||||
memarg: memarg.into(),
|
||||
}),
|
||||
WasmOperator::MemorySize { reserved } => one(Operator::MemorySize { reserved }),
|
||||
WasmOperator::MemoryGrow { reserved } => one(Operator::MemoryGrow { reserved }),
|
||||
WasmOperator::MemorySize { .. } => one(Operator::MemorySize),
|
||||
WasmOperator::MemoryGrow { .. } => one(Operator::MemoryGrow),
|
||||
WasmOperator::I32Const { value } => one(Operator::Const(Value::I32(value))),
|
||||
WasmOperator::I64Const { value } => one(Operator::Const(Value::I64(value))),
|
||||
WasmOperator::F32Const { value } => one(Operator::Const(Value::F32(value.into()))),
|
||||
|
||||
@@ -10,7 +10,7 @@ use memoffset::offset_of;
|
||||
|
||||
use std::{convert::TryInto, mem};
|
||||
use thiserror::Error;
|
||||
use wasmparser::{FuncType, MemoryType, Parser, Payload, Type};
|
||||
use wasmparser::{FuncType, MemoryType, Parser, Payload, ResizableLimits, Type};
|
||||
|
||||
pub trait AsValueType {
|
||||
const TYPE: Type;
|
||||
@@ -104,12 +104,12 @@ pub struct TranslatedModule {
|
||||
ctx: SimpleContext,
|
||||
// TODO: Should we wrap this in a `Mutex` so that calling functions from multiple
|
||||
// threads doesn't cause data races?
|
||||
memory: Option<MemoryType>,
|
||||
memory: Option<ResizableLimits>,
|
||||
}
|
||||
|
||||
impl TranslatedModule {
|
||||
pub fn instantiate(self) -> ExecutableModule {
|
||||
let mem_size = self.memory.map(|m| m.limits.initial).unwrap_or(0) as usize;
|
||||
let mem_size = self.memory.map(|limits| limits.initial).unwrap_or(0) as usize;
|
||||
let mem: BoxSlice<_> = vec![0u8; mem_size * WASM_PAGE_SIZE]
|
||||
.into_boxed_slice()
|
||||
.into();
|
||||
@@ -535,12 +535,19 @@ pub fn translate_only(data: &[u8]) -> Result<TranslatedModule, Error> {
|
||||
|
||||
if !mem.is_empty() {
|
||||
let mem = mem[0];
|
||||
if Some(mem.limits.initial) != mem.limits.maximum {
|
||||
let limits = match mem {
|
||||
MemoryType::M32 {
|
||||
limits,
|
||||
shared: false,
|
||||
} => limits,
|
||||
_ => return Err(Error::Input("unsupported memory".to_string())),
|
||||
};
|
||||
if Some(limits.initial) != limits.maximum {
|
||||
return Err(Error::Input(
|
||||
"Custom memory limits not supported in lightbeam".to_string(),
|
||||
));
|
||||
}
|
||||
output.memory = Some(mem);
|
||||
output.memory = Some(limits);
|
||||
}
|
||||
}
|
||||
Payload::GlobalSection(s) => {
|
||||
|
||||
@@ -13,6 +13,6 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
lightbeam = { path = "..", version = "0.20.0" }
|
||||
wasmparser = "0.59"
|
||||
wasmparser = "0.62"
|
||||
cranelift-codegen = { path = "../../../cranelift/codegen", version = "0.67.0" }
|
||||
wasmtime-environ = { path = "../../environ", version = "0.20.0" }
|
||||
|
||||
@@ -24,7 +24,7 @@ impl Compiler for Lightbeam {
|
||||
&self,
|
||||
translation: &ModuleTranslation,
|
||||
i: DefinedFuncIndex,
|
||||
function_body: &FunctionBodyData<'_>,
|
||||
function_body: FunctionBodyData<'_>,
|
||||
isa: &dyn isa::TargetIsa,
|
||||
) -> Result<CompiledFunction, CompileError> {
|
||||
if translation.tunables.debug_info {
|
||||
@@ -49,7 +49,7 @@ impl Compiler for Lightbeam {
|
||||
offsets: &mut NullOffsetSink,
|
||||
},
|
||||
i.as_u32(),
|
||||
wasmparser::FunctionBody::new(0, function_body.data),
|
||||
function_body.body,
|
||||
)
|
||||
.map_err(|e| CompileError::Codegen(format!("Failed to translate function: {}", e)))?;
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ wasmtime-environ = { path = "../environ", version = "0.20.0" }
|
||||
wasmtime-jit = { path = "../jit", version = "0.20.0" }
|
||||
wasmtime-cache = { path = "../cache", version = "0.20.0", optional = true }
|
||||
wasmtime-profiling = { path = "../profiling", version = "0.20.0" }
|
||||
wasmparser = "0.59.0"
|
||||
target-lexicon = { version = "0.11.0", default-features = false }
|
||||
wasmparser = "0.62.0"
|
||||
anyhow = "1.0.19"
|
||||
region = "2.2.0"
|
||||
libc = "0.2"
|
||||
|
||||
@@ -38,7 +38,7 @@ fn instantiate(
|
||||
let instance = store.add_instance(instance);
|
||||
instance
|
||||
.initialize(
|
||||
config.wasm_bulk_memory,
|
||||
config.features.bulk_memory,
|
||||
&compiled_module.data_initializers(),
|
||||
)
|
||||
.map_err(|e| -> Error {
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::types::{EntityType, ExportType, ExternType, ImportType};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wasmparser::Validator;
|
||||
#[cfg(feature = "cache")]
|
||||
use wasmtime_cache::ModuleCacheEntry;
|
||||
use wasmtime_jit::{CompilationArtifacts, CompiledModule};
|
||||
@@ -238,70 +239,6 @@ impl Module {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
|
||||
Module::validate(engine, binary)?;
|
||||
// Note that the call to `from_binary_unchecked` here should be ok
|
||||
// because we previously validated the binary, meaning we're guaranteed
|
||||
// to pass a valid binary for `engine`.
|
||||
unsafe { Module::from_binary_unchecked(engine, binary) }
|
||||
}
|
||||
|
||||
/// Creates a new WebAssembly `Module` from the given in-memory `binary`
|
||||
/// data, skipping validation and asserting that `binary` is a valid
|
||||
/// WebAssembly module.
|
||||
///
|
||||
/// This function is the same as [`Module::new`] except that it skips the
|
||||
/// call to [`Module::validate`] and it does not support the text format of
|
||||
/// WebAssembly. The WebAssembly binary is not validated for
|
||||
/// correctness and it is simply assumed as valid.
|
||||
///
|
||||
/// For more information about creation of a module and the `engine` argument
|
||||
/// see the documentation of [`Module::new`].
|
||||
///
|
||||
/// # Unsafety
|
||||
///
|
||||
/// This function is `unsafe` due to the unchecked assumption that the input
|
||||
/// `binary` is valid. If the `binary` is not actually a valid wasm binary it
|
||||
/// may cause invalid machine code to get generated, cause panics, etc.
|
||||
///
|
||||
/// It is only safe to call this method if [`Module::validate`] succeeds on
|
||||
/// the same arguments passed to this function.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may fail for many of the same reasons as [`Module::new`].
|
||||
/// While this assumes that the binary is valid it still needs to actually
|
||||
/// be somewhat valid for decoding purposes, and the basics of decoding can
|
||||
/// still fail.
|
||||
pub unsafe fn from_binary_unchecked(engine: &Engine, binary: &[u8]) -> Result<Module> {
|
||||
Module::compile(engine, binary)
|
||||
}
|
||||
|
||||
/// Validates `binary` input data as a WebAssembly binary given the
|
||||
/// configuration in `engine`.
|
||||
///
|
||||
/// This function will perform a speedy validation of the `binary` input
|
||||
/// WebAssembly module (which is in [binary form][binary], the text format
|
||||
/// is not accepted by this function) and return either `Ok` or `Err`
|
||||
/// depending on the results of validation. The `engine` argument indicates
|
||||
/// configuration for WebAssembly features, for example, which are used to
|
||||
/// indicate what should be valid and what shouldn't be.
|
||||
///
|
||||
/// Validation automatically happens as part of [`Module::new`], but is a
|
||||
/// requirement for [`Module::from_binary_unchecked`] to be safe.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If validation fails for any reason (type check error, usage of a feature
|
||||
/// that wasn't enabled, etc) then an error with a description of the
|
||||
/// validation issue will be returned.
|
||||
///
|
||||
/// [binary]: https://webassembly.github.io/spec/core/binary/index.html
|
||||
pub fn validate(engine: &Engine, binary: &[u8]) -> Result<()> {
|
||||
engine.config().validator().validate_all(binary)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn compile(engine: &Engine, binary: &[u8]) -> Result<Self> {
|
||||
#[cfg(feature = "cache")]
|
||||
let artifacts = ModuleCacheEntry::new("wasmtime", engine.cache_config())
|
||||
.get_data((engine.compiler(), binary), |(compiler, binary)| {
|
||||
@@ -323,6 +260,32 @@ impl Module {
|
||||
})
|
||||
}
|
||||
|
||||
/// Validates `binary` input data as a WebAssembly binary given the
|
||||
/// configuration in `engine`.
|
||||
///
|
||||
/// This function will perform a speedy validation of the `binary` input
|
||||
/// WebAssembly module (which is in [binary form][binary], the text format
|
||||
/// is not accepted by this function) and return either `Ok` or `Err`
|
||||
/// depending on the results of validation. The `engine` argument indicates
|
||||
/// configuration for WebAssembly features, for example, which are used to
|
||||
/// indicate what should be valid and what shouldn't be.
|
||||
///
|
||||
/// Validation automatically happens as part of [`Module::new`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If validation fails for any reason (type check error, usage of a feature
|
||||
/// that wasn't enabled, etc) then an error with a description of the
|
||||
/// validation issue will be returned.
|
||||
///
|
||||
/// [binary]: https://webassembly.github.io/spec/core/binary/index.html
|
||||
pub fn validate(engine: &Engine, binary: &[u8]) -> Result<()> {
|
||||
let mut validator = Validator::new();
|
||||
validator.wasm_features(engine.config().features);
|
||||
validator.validate_all(binary)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serialize compilation artifacts to the buffer. See also `deseriaize`.
|
||||
pub fn serialize(&self) -> Result<Vec<u8>> {
|
||||
let artifacts = (
|
||||
|
||||
@@ -12,7 +12,7 @@ use std::path::Path;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::Arc;
|
||||
use target_lexicon::Triple;
|
||||
use wasmparser::Validator;
|
||||
use wasmparser::WasmFeatures;
|
||||
#[cfg(feature = "cache")]
|
||||
use wasmtime_cache::CacheConfig;
|
||||
use wasmtime_environ::settings::{self, Configurable, SetError};
|
||||
@@ -45,11 +45,7 @@ pub struct Config {
|
||||
pub(crate) profiler: Arc<dyn ProfilingAgent>,
|
||||
pub(crate) memory_creator: Option<MemoryCreatorProxy>,
|
||||
pub(crate) max_wasm_stack: usize,
|
||||
wasm_threads: bool,
|
||||
wasm_reference_types: bool,
|
||||
pub(crate) wasm_bulk_memory: bool,
|
||||
wasm_simd: bool,
|
||||
wasm_multi_value: bool,
|
||||
pub(crate) features: WasmFeatures,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -98,11 +94,7 @@ impl Config {
|
||||
profiler: Arc::new(NullProfilerAgent),
|
||||
memory_creator: None,
|
||||
max_wasm_stack: 1 << 20,
|
||||
wasm_threads: false,
|
||||
wasm_reference_types: cfg!(target_arch = "x86_64"),
|
||||
wasm_bulk_memory: true,
|
||||
wasm_simd: false,
|
||||
wasm_multi_value: true,
|
||||
features: WasmFeatures::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +159,7 @@ impl Config {
|
||||
///
|
||||
/// [threads]: https://github.com/webassembly/threads
|
||||
pub fn wasm_threads(&mut self, enable: bool) -> &mut Self {
|
||||
self.wasm_threads = enable;
|
||||
self.features.threads = enable;
|
||||
// The threads proposal depends on the bulk memory proposal
|
||||
if enable {
|
||||
self.wasm_bulk_memory(true);
|
||||
@@ -189,7 +181,7 @@ impl Config {
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/reference-types
|
||||
pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self {
|
||||
self.wasm_reference_types = enable;
|
||||
self.features.reference_types = enable;
|
||||
|
||||
self.flags
|
||||
.set("enable_safepoints", if enable { "true" } else { "false" })
|
||||
@@ -224,7 +216,7 @@ impl Config {
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/simd
|
||||
pub fn wasm_simd(&mut self, enable: bool) -> &mut Self {
|
||||
self.wasm_simd = enable;
|
||||
self.features.simd = enable;
|
||||
let val = if enable { "true" } else { "false" };
|
||||
self.flags
|
||||
.set("enable_simd", val)
|
||||
@@ -242,7 +234,7 @@ impl Config {
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/bulk-memory-operations
|
||||
pub fn wasm_bulk_memory(&mut self, enable: bool) -> &mut Self {
|
||||
self.wasm_bulk_memory = enable;
|
||||
self.features.bulk_memory = enable;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -256,7 +248,7 @@ impl Config {
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/multi-value
|
||||
pub fn wasm_multi_value(&mut self, enable: bool) -> &mut Self {
|
||||
self.wasm_multi_value = enable;
|
||||
self.features.multi_value = enable;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -615,19 +607,9 @@ impl Config {
|
||||
self.isa_flags.clone().finish(settings::Flags::new(flags))
|
||||
}
|
||||
|
||||
pub(crate) fn validator(&self) -> Validator {
|
||||
let mut ret = Validator::new();
|
||||
ret.wasm_threads(self.wasm_threads)
|
||||
.wasm_bulk_memory(self.wasm_bulk_memory)
|
||||
.wasm_multi_value(self.wasm_multi_value)
|
||||
.wasm_reference_types(self.wasm_reference_types)
|
||||
.wasm_simd(self.wasm_simd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
fn build_compiler(&self) -> Compiler {
|
||||
let isa = self.target_isa();
|
||||
Compiler::new(isa, self.strategy, self.tunables.clone())
|
||||
Compiler::new(isa, self.strategy, self.tunables.clone(), self.features)
|
||||
}
|
||||
|
||||
/// Hashes/fingerprints compiler setting to ensure that compatible
|
||||
@@ -666,11 +648,11 @@ impl fmt::Debug for Config {
|
||||
f.debug_struct("Config")
|
||||
.field("debug_info", &self.tunables.debug_info)
|
||||
.field("strategy", &self.strategy)
|
||||
.field("wasm_threads", &self.wasm_threads)
|
||||
.field("wasm_reference_types", &self.wasm_reference_types)
|
||||
.field("wasm_bulk_memory", &self.wasm_bulk_memory)
|
||||
.field("wasm_simd", &self.wasm_simd)
|
||||
.field("wasm_multi_value", &self.wasm_multi_value)
|
||||
.field("wasm_threads", &self.features.threads)
|
||||
.field("wasm_reference_types", &self.features.reference_types)
|
||||
.field("wasm_bulk_memory", &self.features.bulk_memory)
|
||||
.field("wasm_simd", &self.features.simd)
|
||||
.field("wasm_multi_value", &self.features.multi_value)
|
||||
.field(
|
||||
"flags",
|
||||
&settings::Flags::new(self.flags.clone()).to_string(),
|
||||
|
||||
Reference in New Issue
Block a user