Start compiling module-linking modules (#2093)
This commit is intended to be the first of many in implementing the module linking proposal. At this time this builds on #2059 so it shouldn't land yet. The goal of this commit is to compile bare-bones modules which use module linking, e.g. those with nested modules. My hope with module linking is that almost everything in wasmtime only needs mild refactorings to handle it. The goal is that all per-module structures are still per-module and at the top level there's just a `Vec` containing a bunch of modules. That's implemented currently where `wasmtime::Module` contains `Arc<[CompiledModule]>` and an index of which one it's pointing to. This should enable serialization/deserialization of any module in a nested modules scenario, no matter how you got it. Tons of features of the module linking proposal are missing from this commit. For example instantiation flat out doesn't work, nor does import/export of modules or instances. That'll be coming as future commits, but the purpose here is to start laying groundwork in Wasmtime for handling lots of modules in lots of places.
This commit is contained in:
@@ -847,4 +847,31 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment {
|
|||||||
fn wasm_features(&self) -> WasmFeatures {
|
fn wasm_features(&self) -> WasmFeatures {
|
||||||
WasmFeatures::default()
|
WasmFeatures::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates that this module will have `amount` submodules.
|
||||||
|
///
|
||||||
|
/// Note that this is just child modules of this module, and each child
|
||||||
|
/// module may have yet more submodules.
|
||||||
|
fn reserve_modules(&mut self, amount: u32) {
|
||||||
|
drop(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called at the beginning of translating a module.
|
||||||
|
///
|
||||||
|
/// The `index` argument is a monotonically increasing index which
|
||||||
|
/// corresponds to the nth module that's being translated. This is not the
|
||||||
|
/// 32-bit index in the current module's index space. For example the first
|
||||||
|
/// call to `module_start` will have index 0.
|
||||||
|
///
|
||||||
|
/// Note that for nested modules this may be called multiple times.
|
||||||
|
fn module_start(&mut self, index: usize) {
|
||||||
|
drop(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called at the end of translating a module.
|
||||||
|
///
|
||||||
|
/// Note that for nested modules this may be called multiple times.
|
||||||
|
fn module_end(&mut self, index: usize) {
|
||||||
|
drop(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use crate::sections_translator::{
|
|||||||
};
|
};
|
||||||
use crate::state::ModuleTranslationState;
|
use crate::state::ModuleTranslationState;
|
||||||
use cranelift_codegen::timing;
|
use cranelift_codegen::timing;
|
||||||
|
use std::prelude::v1::*;
|
||||||
use wasmparser::{NameSectionReader, Parser, Payload, Validator};
|
use wasmparser::{NameSectionReader, Parser, Payload, Validator};
|
||||||
|
|
||||||
/// Translate a sequence of bytes forming a valid Wasm binary into a list of valid Cranelift IR
|
/// Translate a sequence of bytes forming a valid Wasm binary into a list of valid Cranelift IR
|
||||||
@@ -20,14 +21,23 @@ pub fn translate_module<'data>(
|
|||||||
let mut module_translation_state = ModuleTranslationState::new();
|
let mut module_translation_state = ModuleTranslationState::new();
|
||||||
let mut validator = Validator::new();
|
let mut validator = Validator::new();
|
||||||
validator.wasm_features(environ.wasm_features());
|
validator.wasm_features(environ.wasm_features());
|
||||||
|
let mut stack = Vec::new();
|
||||||
|
let mut modules = 1;
|
||||||
|
let mut cur_module = 0;
|
||||||
|
|
||||||
for payload in Parser::new(0).parse_all(data) {
|
for payload in Parser::new(0).parse_all(data) {
|
||||||
match payload? {
|
match payload? {
|
||||||
Payload::Version { num, range } => {
|
Payload::Version { num, range } => {
|
||||||
validator.version(num, &range)?;
|
validator.version(num, &range)?;
|
||||||
|
environ.module_start(cur_module);
|
||||||
}
|
}
|
||||||
Payload::End => {
|
Payload::End => {
|
||||||
validator.end()?;
|
validator.end()?;
|
||||||
|
environ.module_end(cur_module);
|
||||||
|
if let Some((other, other_index)) = stack.pop() {
|
||||||
|
validator = other;
|
||||||
|
cur_module = other_index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Payload::TypeSection(types) => {
|
Payload::TypeSection(types) => {
|
||||||
@@ -97,7 +107,7 @@ pub fn translate_module<'data>(
|
|||||||
|
|
||||||
Payload::ModuleSection(s) => {
|
Payload::ModuleSection(s) => {
|
||||||
validator.module_section(&s)?;
|
validator.module_section(&s)?;
|
||||||
unimplemented!("module linking not implemented yet")
|
environ.reserve_modules(s.get_count());
|
||||||
}
|
}
|
||||||
Payload::InstanceSection(s) => {
|
Payload::InstanceSection(s) => {
|
||||||
validator.instance_section(&s)?;
|
validator.instance_section(&s)?;
|
||||||
@@ -113,11 +123,14 @@ pub fn translate_module<'data>(
|
|||||||
size: _,
|
size: _,
|
||||||
} => {
|
} => {
|
||||||
validator.module_code_section_start(count, &range)?;
|
validator.module_code_section_start(count, &range)?;
|
||||||
unimplemented!("module linking not implemented yet")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Payload::ModuleCodeSectionEntry { .. } => {
|
Payload::ModuleCodeSectionEntry { .. } => {
|
||||||
unimplemented!("module linking not implemented yet")
|
let subvalidator = validator.module_code_section_entry();
|
||||||
|
stack.push((validator, cur_module));
|
||||||
|
validator = subvalidator;
|
||||||
|
cur_module = modules;
|
||||||
|
modules += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Payload::CustomSection {
|
Payload::CustomSection {
|
||||||
|
|||||||
@@ -41,25 +41,43 @@ pub fn parse_type_section(
|
|||||||
environ.reserve_signatures(count)?;
|
environ.reserve_signatures(count)?;
|
||||||
|
|
||||||
for entry in types {
|
for entry in types {
|
||||||
if let Ok(TypeDef::Func(wasm_func_ty)) = entry {
|
match entry? {
|
||||||
let mut sig =
|
TypeDef::Func(wasm_func_ty) => {
|
||||||
Signature::new(ModuleEnvironment::target_config(environ).default_call_conv);
|
let mut sig =
|
||||||
sig.params.extend(wasm_func_ty.params.iter().map(|ty| {
|
Signature::new(ModuleEnvironment::target_config(environ).default_call_conv);
|
||||||
let cret_arg: ir::Type = type_to_type(*ty, environ)
|
sig.params.extend(wasm_func_ty.params.iter().map(|ty| {
|
||||||
.expect("only numeric types are supported in function signatures");
|
let cret_arg: ir::Type = type_to_type(*ty, environ)
|
||||||
AbiParam::new(cret_arg)
|
.expect("only numeric types are supported in function signatures");
|
||||||
}));
|
AbiParam::new(cret_arg)
|
||||||
sig.returns.extend(wasm_func_ty.returns.iter().map(|ty| {
|
}));
|
||||||
let cret_arg: ir::Type = type_to_type(*ty, environ)
|
sig.returns.extend(wasm_func_ty.returns.iter().map(|ty| {
|
||||||
.expect("only numeric types are supported in function signatures");
|
let cret_arg: ir::Type = type_to_type(*ty, environ)
|
||||||
AbiParam::new(cret_arg)
|
.expect("only numeric types are supported in function signatures");
|
||||||
}));
|
AbiParam::new(cret_arg)
|
||||||
environ.declare_signature(wasm_func_ty.clone().try_into()?, sig)?;
|
}));
|
||||||
module_translation_state
|
environ.declare_signature(wasm_func_ty.clone().try_into()?, sig)?;
|
||||||
.wasm_types
|
module_translation_state
|
||||||
.push((wasm_func_ty.params, wasm_func_ty.returns));
|
.wasm_types
|
||||||
} else {
|
.push((wasm_func_ty.params, wasm_func_ty.returns));
|
||||||
unimplemented!("module linking not implemented yet")
|
}
|
||||||
|
|
||||||
|
// Not implemented yet for module linking. Push dummy function types
|
||||||
|
// though to keep the function type index space consistent. We'll
|
||||||
|
// want an actual implementation here that handles this eventually.
|
||||||
|
TypeDef::Module(_) | TypeDef::Instance(_) => {
|
||||||
|
let sig =
|
||||||
|
Signature::new(ModuleEnvironment::target_config(environ).default_call_conv);
|
||||||
|
environ.declare_signature(
|
||||||
|
crate::environ::WasmFuncType {
|
||||||
|
params: Box::new([]),
|
||||||
|
returns: Box::new([]),
|
||||||
|
},
|
||||||
|
sig,
|
||||||
|
)?;
|
||||||
|
module_translation_state
|
||||||
|
.wasm_types
|
||||||
|
.push((Box::new([]), Box::new([])));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ use std::sync::Mutex;
|
|||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
CompileError, CompiledFunction, Compiler, FunctionAddressMap, FunctionBodyData,
|
CompileError, CompiledFunction, Compiler, FunctionAddressMap, FunctionBodyData,
|
||||||
InstructionAddressMap, ModuleTranslation, Relocation, RelocationTarget, StackMapInformation,
|
InstructionAddressMap, ModuleTranslation, Relocation, RelocationTarget, StackMapInformation,
|
||||||
TrapInformation,
|
TrapInformation, Tunables,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod func_environ;
|
mod func_environ;
|
||||||
@@ -356,9 +356,9 @@ impl Compiler for Cranelift {
|
|||||||
func_index: DefinedFuncIndex,
|
func_index: DefinedFuncIndex,
|
||||||
mut input: FunctionBodyData<'_>,
|
mut input: FunctionBodyData<'_>,
|
||||||
isa: &dyn isa::TargetIsa,
|
isa: &dyn isa::TargetIsa,
|
||||||
|
tunables: &Tunables,
|
||||||
) -> Result<CompiledFunction, CompileError> {
|
) -> Result<CompiledFunction, CompileError> {
|
||||||
let module = &translation.module;
|
let module = &translation.module;
|
||||||
let tunables = &translation.tunables;
|
|
||||||
let func_index = module.func_index(func_index);
|
let func_index = module.func_index(func_index);
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.func.name = get_func_name(func_index);
|
context.func.name = get_func_name(func_index);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! A `Compilation` contains the compiled function bodies for a WebAssembly
|
//! A `Compilation` contains the compiled function bodies for a WebAssembly
|
||||||
//! module.
|
//! module.
|
||||||
|
|
||||||
use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation};
|
use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation, Tunables};
|
||||||
use cranelift_codegen::{binemit, ir, isa, isa::unwind::UnwindInfo};
|
use cranelift_codegen::{binemit, ir, isa, isa::unwind::UnwindInfo};
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
|
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
|
||||||
@@ -103,5 +103,6 @@ pub trait Compiler: Send + Sync {
|
|||||||
index: DefinedFuncIndex,
|
index: DefinedFuncIndex,
|
||||||
data: FunctionBodyData<'_>,
|
data: FunctionBodyData<'_>,
|
||||||
isa: &dyn isa::TargetIsa,
|
isa: &dyn isa::TargetIsa,
|
||||||
|
tunables: &Tunables,
|
||||||
) -> Result<CompiledFunction, CompileError>;
|
) -> Result<CompiledFunction, CompileError>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,6 +325,12 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Module {
|
||||||
|
fn default() -> Module {
|
||||||
|
Module::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod passive_data_serde {
|
mod passive_data_serde {
|
||||||
use super::{Arc, DataIndex, HashMap};
|
use super::{Arc, DataIndex, HashMap};
|
||||||
use serde::{de::MapAccess, de::Visitor, ser::SerializeMap, Deserializer, Serializer};
|
use serde::{de::MapAccess, de::Visitor, ser::SerializeMap, Deserializer, Serializer};
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ use cranelift_codegen::isa::TargetFrontendConfig;
|
|||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{
|
use cranelift_wasm::{
|
||||||
self, translate_module, DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex,
|
self, translate_module, DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex,
|
||||||
Memory, MemoryIndex, ModuleTranslationState, SignatureIndex, Table, TableIndex,
|
Memory, MemoryIndex, SignatureIndex, Table, TableIndex, TargetEnvironment, WasmError,
|
||||||
TargetEnvironment, WasmError, WasmFuncType, WasmResult,
|
WasmFuncType, WasmResult,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::mem;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmparser::Type as WasmType;
|
use wasmparser::Type as WasmType;
|
||||||
@@ -19,19 +20,29 @@ use wasmparser::{FuncValidator, FunctionBody, ValidatorResources, WasmFeatures};
|
|||||||
|
|
||||||
/// Object containing the standalone environment information.
|
/// Object containing the standalone environment information.
|
||||||
pub struct ModuleEnvironment<'data> {
|
pub struct ModuleEnvironment<'data> {
|
||||||
/// The result to be filled in.
|
/// The current module being translated
|
||||||
result: ModuleTranslation<'data>,
|
result: ModuleTranslation<'data>,
|
||||||
code_index: u32,
|
|
||||||
|
/// Modules which have finished translation. This only really applies for
|
||||||
|
/// the module linking proposal.
|
||||||
|
results: Vec<ModuleTranslation<'data>>,
|
||||||
|
|
||||||
|
/// Modules which are in-progress for being translated (our parents) and
|
||||||
|
/// we'll resume once we finish the current module. This is only applicable
|
||||||
|
/// with the module linking proposal.
|
||||||
|
in_progress: Vec<ModuleTranslation<'data>>,
|
||||||
|
|
||||||
|
// Various bits and pieces of configuration
|
||||||
features: WasmFeatures,
|
features: WasmFeatures,
|
||||||
|
target_config: TargetFrontendConfig,
|
||||||
|
tunables: Tunables,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of translating via `ModuleEnvironment`. Function bodies are not
|
/// The result of translating via `ModuleEnvironment`. Function bodies are not
|
||||||
/// yet translated, and data initializers have not yet been copied out of the
|
/// yet translated, and data initializers have not yet been copied out of the
|
||||||
/// original buffer.
|
/// original buffer.
|
||||||
|
#[derive(Default)]
|
||||||
pub struct ModuleTranslation<'data> {
|
pub struct ModuleTranslation<'data> {
|
||||||
/// Compilation setting flags.
|
|
||||||
pub target_config: TargetFrontendConfig,
|
|
||||||
|
|
||||||
/// Module information.
|
/// Module information.
|
||||||
pub module: Module,
|
pub module: Module,
|
||||||
|
|
||||||
@@ -44,14 +55,14 @@ pub struct ModuleTranslation<'data> {
|
|||||||
/// References to the data initializers.
|
/// References to the data initializers.
|
||||||
pub data_initializers: Vec<DataInitializer<'data>>,
|
pub data_initializers: Vec<DataInitializer<'data>>,
|
||||||
|
|
||||||
/// Tunable parameters.
|
|
||||||
pub tunables: Tunables,
|
|
||||||
|
|
||||||
/// The decoded Wasm types for the module.
|
|
||||||
pub module_translation: Option<ModuleTranslationState>,
|
|
||||||
|
|
||||||
/// DWARF debug information, if enabled, parsed from the module.
|
/// DWARF debug information, if enabled, parsed from the module.
|
||||||
pub debuginfo: Option<DebugInfoData<'data>>,
|
pub debuginfo: DebugInfoData<'data>,
|
||||||
|
|
||||||
|
/// Indexes into the returned list of translations that are submodules of
|
||||||
|
/// this module.
|
||||||
|
pub submodules: Vec<usize>,
|
||||||
|
|
||||||
|
code_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains function data: byte code and its offset in the module.
|
/// Contains function data: byte code and its offset in the module.
|
||||||
@@ -111,36 +122,25 @@ impl<'data> ModuleEnvironment<'data> {
|
|||||||
features: &WasmFeatures,
|
features: &WasmFeatures,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
result: ModuleTranslation {
|
result: ModuleTranslation::default(),
|
||||||
target_config,
|
results: Vec::with_capacity(1),
|
||||||
module: Module::new(),
|
in_progress: Vec::new(),
|
||||||
native_signatures: PrimaryMap::new(),
|
target_config,
|
||||||
function_body_inputs: PrimaryMap::new(),
|
tunables: tunables.clone(),
|
||||||
data_initializers: Vec::new(),
|
|
||||||
tunables: tunables.clone(),
|
|
||||||
module_translation: None,
|
|
||||||
debuginfo: if tunables.debug_info {
|
|
||||||
Some(DebugInfoData::default())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
},
|
|
||||||
code_index: 0,
|
|
||||||
features: *features,
|
features: *features,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pointer_type(&self) -> ir::Type {
|
fn pointer_type(&self) -> ir::Type {
|
||||||
self.result.target_config.pointer_type()
|
self.target_config.pointer_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translate a wasm module using this environment. This consumes the
|
/// Translate a wasm module using this environment. This consumes the
|
||||||
/// `ModuleEnvironment` and produces a `ModuleTranslation`.
|
/// `ModuleEnvironment` and produces a `ModuleTranslation`.
|
||||||
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<ModuleTranslation<'data>> {
|
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<Vec<ModuleTranslation<'data>>> {
|
||||||
assert!(self.result.module_translation.is_none());
|
translate_module(data, &mut self)?;
|
||||||
let module_translation = translate_module(data, &mut self)?;
|
assert!(self.results.len() > 0);
|
||||||
self.result.module_translation = Some(module_translation);
|
Ok(self.results)
|
||||||
Ok(self.result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_export(&mut self, export: EntityIndex, name: &str) -> WasmResult<()> {
|
fn declare_export(&mut self, export: EntityIndex, name: &str) -> WasmResult<()> {
|
||||||
@@ -152,13 +152,13 @@ impl<'data> ModuleEnvironment<'data> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn register_dwarf_section(&mut self, name: &str, data: &'data [u8]) {
|
fn register_dwarf_section(&mut self, name: &str, data: &'data [u8]) {
|
||||||
let info = match &mut self.result.debuginfo {
|
if !self.tunables.debug_info {
|
||||||
Some(info) => info,
|
return;
|
||||||
None => return,
|
}
|
||||||
};
|
|
||||||
if !name.starts_with(".debug_") {
|
if !name.starts_with(".debug_") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let info = &mut self.result.debuginfo;
|
||||||
let dwarf = &mut info.dwarf;
|
let dwarf = &mut info.dwarf;
|
||||||
let endian = gimli::LittleEndian;
|
let endian = gimli::LittleEndian;
|
||||||
let slice = gimli::EndianSlice::new(data, endian);
|
let slice = gimli::EndianSlice::new(data, endian);
|
||||||
@@ -190,7 +190,7 @@ impl<'data> ModuleEnvironment<'data> {
|
|||||||
|
|
||||||
impl<'data> TargetEnvironment for ModuleEnvironment<'data> {
|
impl<'data> TargetEnvironment for ModuleEnvironment<'data> {
|
||||||
fn target_config(&self) -> TargetFrontendConfig {
|
fn target_config(&self) -> TargetFrontendConfig {
|
||||||
self.result.target_config
|
self.target_config
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reference_type(&self, ty: cranelift_wasm::WasmType) -> ir::Type {
|
fn reference_type(&self, ty: cranelift_wasm::WasmType) -> ir::Type {
|
||||||
@@ -242,9 +242,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
EntityIndex::Function(func_index),
|
EntityIndex::Function(func_index),
|
||||||
));
|
));
|
||||||
self.result.module.num_imported_funcs += 1;
|
self.result.module.num_imported_funcs += 1;
|
||||||
if let Some(info) = &mut self.result.debuginfo {
|
self.result.debuginfo.wasm_file.imported_func_count += 1;
|
||||||
info.wasm_file.imported_func_count += 1;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +252,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
self.result.module.num_imported_tables,
|
self.result.module.num_imported_tables,
|
||||||
"Imported tables must be declared first"
|
"Imported tables must be declared first"
|
||||||
);
|
);
|
||||||
let plan = TablePlan::for_table(table, &self.result.tunables);
|
let plan = TablePlan::for_table(table, &self.tunables);
|
||||||
let table_index = self.result.module.table_plans.push(plan);
|
let table_index = self.result.module.table_plans.push(plan);
|
||||||
self.result.module.imports.push((
|
self.result.module.imports.push((
|
||||||
module.to_owned(),
|
module.to_owned(),
|
||||||
@@ -279,7 +277,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
if memory.shared {
|
if memory.shared {
|
||||||
return Err(WasmError::Unsupported("shared memories".to_owned()));
|
return Err(WasmError::Unsupported("shared memories".to_owned()));
|
||||||
}
|
}
|
||||||
let plan = MemoryPlan::for_memory(memory, &self.result.tunables);
|
let plan = MemoryPlan::for_memory(memory, &self.tunables);
|
||||||
let memory_index = self.result.module.memory_plans.push(plan);
|
let memory_index = self.result.module.memory_plans.push(plan);
|
||||||
self.result.module.imports.push((
|
self.result.module.imports.push((
|
||||||
module.to_owned(),
|
module.to_owned(),
|
||||||
@@ -336,7 +334,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn declare_table(&mut self, table: Table) -> WasmResult<()> {
|
fn declare_table(&mut self, table: Table) -> WasmResult<()> {
|
||||||
let plan = TablePlan::for_table(table, &self.result.tunables);
|
let plan = TablePlan::for_table(table, &self.tunables);
|
||||||
self.result.module.table_plans.push(plan);
|
self.result.module.table_plans.push(plan);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -353,7 +351,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
if memory.shared {
|
if memory.shared {
|
||||||
return Err(WasmError::Unsupported("shared memories".to_owned()));
|
return Err(WasmError::Unsupported("shared memories".to_owned()));
|
||||||
}
|
}
|
||||||
let plan = MemoryPlan::for_memory(memory, &self.result.tunables);
|
let plan = MemoryPlan::for_memory(memory, &self.tunables);
|
||||||
self.result.module.memory_plans.push(plan);
|
self.result.module.memory_plans.push(plan);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -444,9 +442,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reserve_function_bodies(&mut self, _count: u32, offset: u64) {
|
fn reserve_function_bodies(&mut self, _count: u32, offset: u64) {
|
||||||
if let Some(info) = &mut self.result.debuginfo {
|
self.result.debuginfo.wasm_file.code_section_offset = offset;
|
||||||
info.wasm_file.code_section_offset = offset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn define_function_body(
|
fn define_function_body(
|
||||||
@@ -454,8 +450,8 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
validator: FuncValidator<ValidatorResources>,
|
validator: FuncValidator<ValidatorResources>,
|
||||||
body: FunctionBody<'data>,
|
body: FunctionBody<'data>,
|
||||||
) -> WasmResult<()> {
|
) -> WasmResult<()> {
|
||||||
if let Some(info) = &mut self.result.debuginfo {
|
if self.tunables.debug_info {
|
||||||
let func_index = self.code_index + self.result.module.num_imported_funcs as u32;
|
let func_index = self.result.code_index + self.result.module.num_imported_funcs as u32;
|
||||||
let func_index = FuncIndex::from_u32(func_index);
|
let func_index = FuncIndex::from_u32(func_index);
|
||||||
let sig_index = self.result.module.functions[func_index];
|
let sig_index = self.result.module.functions[func_index];
|
||||||
let sig = &self.result.module.signatures[sig_index];
|
let sig = &self.result.module.signatures[sig_index];
|
||||||
@@ -463,15 +459,19 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
for pair in body.get_locals_reader()? {
|
for pair in body.get_locals_reader()? {
|
||||||
locals.push(pair?);
|
locals.push(pair?);
|
||||||
}
|
}
|
||||||
info.wasm_file.funcs.push(FunctionMetadata {
|
self.result
|
||||||
locals: locals.into_boxed_slice(),
|
.debuginfo
|
||||||
params: sig.params.iter().cloned().map(|i| i.into()).collect(),
|
.wasm_file
|
||||||
});
|
.funcs
|
||||||
|
.push(FunctionMetadata {
|
||||||
|
locals: locals.into_boxed_slice(),
|
||||||
|
params: sig.params.iter().cloned().map(|i| i.into()).collect(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
self.result
|
self.result
|
||||||
.function_body_inputs
|
.function_body_inputs
|
||||||
.push(FunctionBodyData { validator, body });
|
.push(FunctionBodyData { validator, body });
|
||||||
self.code_index += 1;
|
self.result.code_index += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,8 +520,8 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
|
|
||||||
fn declare_module_name(&mut self, name: &'data str) {
|
fn declare_module_name(&mut self, name: &'data str) {
|
||||||
self.result.module.name = Some(name.to_string());
|
self.result.module.name = Some(name.to_string());
|
||||||
if let Some(info) = &mut self.result.debuginfo {
|
if self.tunables.debug_info {
|
||||||
info.name_section.module_name = Some(name);
|
self.result.debuginfo.name_section.module_name = Some(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,16 +530,20 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
|||||||
.module
|
.module
|
||||||
.func_names
|
.func_names
|
||||||
.insert(func_index, name.to_string());
|
.insert(func_index, name.to_string());
|
||||||
if let Some(info) = &mut self.result.debuginfo {
|
if self.tunables.debug_info {
|
||||||
info.name_section
|
self.result
|
||||||
|
.debuginfo
|
||||||
|
.name_section
|
||||||
.func_names
|
.func_names
|
||||||
.insert(func_index.as_u32(), name);
|
.insert(func_index.as_u32(), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_local_name(&mut self, func_index: FuncIndex, local: u32, name: &'data str) {
|
fn declare_local_name(&mut self, func_index: FuncIndex, local: u32, name: &'data str) {
|
||||||
if let Some(info) = &mut self.result.debuginfo {
|
if self.tunables.debug_info {
|
||||||
info.name_section
|
self.result
|
||||||
|
.debuginfo
|
||||||
|
.name_section
|
||||||
.locals_names
|
.locals_names
|
||||||
.entry(func_index.as_u32())
|
.entry(func_index.as_u32())
|
||||||
.or_insert(HashMap::new())
|
.or_insert(HashMap::new())
|
||||||
@@ -574,6 +578,34 @@ and for re-adding support for interface types you can see this issue:
|
|||||||
fn wasm_features(&self) -> WasmFeatures {
|
fn wasm_features(&self) -> WasmFeatures {
|
||||||
self.features
|
self.features
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reserve_modules(&mut self, amount: u32) {
|
||||||
|
let extra = self.results.capacity() + (amount as usize) - self.results.len();
|
||||||
|
self.results.reserve(extra);
|
||||||
|
self.result.submodules.reserve(amount as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_start(&mut self, index: usize) {
|
||||||
|
// skip the first module since `self.result` is already empty and we'll
|
||||||
|
// be translating into that.
|
||||||
|
if index > 0 {
|
||||||
|
let in_progress = mem::replace(&mut self.result, ModuleTranslation::default());
|
||||||
|
self.in_progress.push(in_progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_end(&mut self, index: usize) {
|
||||||
|
let to_continue = match self.in_progress.pop() {
|
||||||
|
Some(m) => m,
|
||||||
|
None => {
|
||||||
|
assert_eq!(index, 0);
|
||||||
|
ModuleTranslation::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let finished = mem::replace(&mut self.result, to_continue);
|
||||||
|
self.result.submodules.push(self.results.len());
|
||||||
|
self.results.push(finished);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add environment-specific function parameters.
|
/// Add environment-specific function parameters.
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
use crate::instantiate::SetupError;
|
use crate::instantiate::SetupError;
|
||||||
use crate::object::{build_object, ObjectUnwindInfo};
|
use crate::object::{build_object, ObjectUnwindInfo};
|
||||||
use object::write::Object;
|
use object::write::Object;
|
||||||
|
#[cfg(feature = "parallel-compilation")]
|
||||||
|
use rayon::prelude::*;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use wasmparser::WasmFeatures;
|
use wasmparser::WasmFeatures;
|
||||||
@@ -127,31 +129,21 @@ impl Compiler {
|
|||||||
translation: &mut ModuleTranslation,
|
translation: &mut ModuleTranslation,
|
||||||
) -> Result<Compilation, SetupError> {
|
) -> Result<Compilation, SetupError> {
|
||||||
let functions = mem::take(&mut translation.function_body_inputs);
|
let functions = mem::take(&mut translation.function_body_inputs);
|
||||||
cfg_if::cfg_if! {
|
let functions = functions.into_iter().collect::<Vec<_>>();
|
||||||
if #[cfg(feature = "parallel-compilation")] {
|
let funcs = maybe_parallel!(functions.(into_iter | into_par_iter))
|
||||||
use rayon::prelude::*;
|
|
||||||
let iter = functions
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.into_par_iter();
|
|
||||||
} else {
|
|
||||||
let iter = functions.into_iter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let funcs = iter
|
|
||||||
.map(|(index, func)| {
|
.map(|(index, func)| {
|
||||||
self.compiler
|
self.compiler
|
||||||
.compile_function(translation, index, func, &*self.isa)
|
.compile_function(translation, index, func, &*self.isa, &self.tunables)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<CompiledFunctions>();
|
.collect::<CompiledFunctions>();
|
||||||
|
|
||||||
let dwarf_sections = if translation.debuginfo.is_some() && !funcs.is_empty() {
|
let dwarf_sections = if self.tunables.debug_info && !funcs.is_empty() {
|
||||||
transform_dwarf_data(
|
transform_dwarf_data(
|
||||||
&*self.isa,
|
&*self.isa,
|
||||||
&translation.module,
|
&translation.module,
|
||||||
translation.debuginfo.as_ref().unwrap(),
|
&translation.debuginfo,
|
||||||
&funcs,
|
&funcs,
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
@@ -191,6 +183,9 @@ impl Hash for Compiler {
|
|||||||
isa.frontend_config().hash(hasher);
|
isa.frontend_config().hash(hasher);
|
||||||
tunables.hash(hasher);
|
tunables.hash(hasher);
|
||||||
|
|
||||||
|
// Catch accidental bugs of reusing across crate versions.
|
||||||
|
env!("CARGO_PKG_VERSION").hash(hasher);
|
||||||
|
|
||||||
// TODO: ... and should we hash anything else? There's a lot of stuff in
|
// TODO: ... and should we hash anything else? There's a lot of stuff in
|
||||||
// `TargetIsa`, like registers/encodings/etc. Should we be hashing that
|
// `TargetIsa`, like registers/encodings/etc. Should we be hashing that
|
||||||
// too? It seems like wasmtime doesn't configure it too too much, but
|
// too? It seems like wasmtime doesn't configure it too too much, but
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use crate::compiler::{Compilation, Compiler};
|
|||||||
use crate::link::link_module;
|
use crate::link::link_module;
|
||||||
use crate::object::ObjectUnwindInfo;
|
use crate::object::ObjectUnwindInfo;
|
||||||
use object::File as ObjectFile;
|
use object::File as ObjectFile;
|
||||||
|
#[cfg(feature = "parallel-compilation")]
|
||||||
|
use rayon::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -71,6 +73,68 @@ pub struct CompilationArtifacts {
|
|||||||
debug_info: bool,
|
debug_info: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CompilationArtifacts {
|
||||||
|
/// Creates a `CompilationArtifacts` for a singular translated wasm module.
|
||||||
|
pub fn build(
|
||||||
|
compiler: &Compiler,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<Vec<CompilationArtifacts>, SetupError> {
|
||||||
|
let translations = ModuleEnvironment::new(
|
||||||
|
compiler.frontend_config(),
|
||||||
|
compiler.tunables(),
|
||||||
|
compiler.features(),
|
||||||
|
)
|
||||||
|
.translate(data)
|
||||||
|
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
|
||||||
|
|
||||||
|
maybe_parallel!(translations.(into_iter | into_par_iter))
|
||||||
|
.map(|mut translation| {
|
||||||
|
let Compilation {
|
||||||
|
obj,
|
||||||
|
unwind_info,
|
||||||
|
funcs,
|
||||||
|
} = compiler.compile(&mut translation)?;
|
||||||
|
|
||||||
|
let ModuleTranslation {
|
||||||
|
module,
|
||||||
|
data_initializers,
|
||||||
|
..
|
||||||
|
} = translation;
|
||||||
|
|
||||||
|
let data_initializers = data_initializers
|
||||||
|
.into_iter()
|
||||||
|
.map(OwnedDataInitializer::new)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_boxed_slice();
|
||||||
|
|
||||||
|
let obj = obj.write().map_err(|_| {
|
||||||
|
SetupError::Instantiate(InstantiationError::Resource(
|
||||||
|
"failed to create image memory".to_string(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(CompilationArtifacts {
|
||||||
|
module,
|
||||||
|
obj: obj.into_boxed_slice(),
|
||||||
|
unwind_info: unwind_info.into_boxed_slice(),
|
||||||
|
data_initializers,
|
||||||
|
funcs: funcs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, func)| FunctionInfo {
|
||||||
|
stack_maps: func.stack_maps,
|
||||||
|
traps: func.traps,
|
||||||
|
address_map: func.address_map,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
debug_info: compiler.tunables().debug_info,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, SetupError>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FinishedFunctions(PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>);
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
struct FunctionInfo {
|
struct FunctionInfo {
|
||||||
traps: Vec<TrapInformation>,
|
traps: Vec<TrapInformation>,
|
||||||
@@ -78,63 +142,6 @@ struct FunctionInfo {
|
|||||||
stack_maps: Vec<StackMapInformation>,
|
stack_maps: Vec<StackMapInformation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompilationArtifacts {
|
|
||||||
/// Builds compilation artifacts.
|
|
||||||
pub fn build(compiler: &Compiler, data: &[u8]) -> Result<Self, SetupError> {
|
|
||||||
let environ = ModuleEnvironment::new(
|
|
||||||
compiler.frontend_config(),
|
|
||||||
compiler.tunables(),
|
|
||||||
compiler.features(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut translation = environ
|
|
||||||
.translate(data)
|
|
||||||
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
|
|
||||||
|
|
||||||
let Compilation {
|
|
||||||
obj,
|
|
||||||
unwind_info,
|
|
||||||
funcs,
|
|
||||||
} = compiler.compile(&mut translation)?;
|
|
||||||
|
|
||||||
let ModuleTranslation {
|
|
||||||
module,
|
|
||||||
data_initializers,
|
|
||||||
..
|
|
||||||
} = translation;
|
|
||||||
|
|
||||||
let data_initializers = data_initializers
|
|
||||||
.into_iter()
|
|
||||||
.map(OwnedDataInitializer::new)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.into_boxed_slice();
|
|
||||||
|
|
||||||
let obj = obj.write().map_err(|_| {
|
|
||||||
SetupError::Instantiate(InstantiationError::Resource(
|
|
||||||
"failed to create image memory".to_string(),
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
module,
|
|
||||||
obj: obj.into_boxed_slice(),
|
|
||||||
unwind_info: unwind_info.into_boxed_slice(),
|
|
||||||
data_initializers,
|
|
||||||
funcs: funcs
|
|
||||||
.into_iter()
|
|
||||||
.map(|(_, func)| FunctionInfo {
|
|
||||||
stack_maps: func.stack_maps,
|
|
||||||
traps: func.traps,
|
|
||||||
address_map: func.address_map,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
debug_info: compiler.tunables().debug_info,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FinishedFunctions(PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>);
|
|
||||||
|
|
||||||
unsafe impl Send for FinishedFunctions {}
|
unsafe impl Send for FinishedFunctions {}
|
||||||
unsafe impl Sync for FinishedFunctions {}
|
unsafe impl Sync for FinishedFunctions {}
|
||||||
|
|
||||||
@@ -147,25 +154,24 @@ pub struct ModuleCode {
|
|||||||
|
|
||||||
/// A compiled wasm module, ready to be instantiated.
|
/// A compiled wasm module, ready to be instantiated.
|
||||||
pub struct CompiledModule {
|
pub struct CompiledModule {
|
||||||
|
artifacts: CompilationArtifacts,
|
||||||
module: Arc<Module>,
|
module: Arc<Module>,
|
||||||
code: Arc<ModuleCode>,
|
code: Arc<ModuleCode>,
|
||||||
finished_functions: FinishedFunctions,
|
finished_functions: FinishedFunctions,
|
||||||
trampolines: PrimaryMap<SignatureIndex, VMTrampoline>,
|
trampolines: PrimaryMap<SignatureIndex, VMTrampoline>,
|
||||||
data_initializers: Box<[OwnedDataInitializer]>,
|
|
||||||
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
|
|
||||||
obj: Box<[u8]>,
|
|
||||||
unwind_info: Box<[ObjectUnwindInfo]>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompiledModule {
|
impl CompiledModule {
|
||||||
/// Compile a data buffer into a `CompiledModule`, which may then be instantiated.
|
/// Creates a list of compiled modules from the given list of compilation
|
||||||
pub fn new<'data>(
|
/// artifacts.
|
||||||
compiler: &Compiler,
|
pub fn from_artifacts_list(
|
||||||
data: &'data [u8],
|
artifacts: Vec<CompilationArtifacts>,
|
||||||
|
isa: &dyn TargetIsa,
|
||||||
profiler: &dyn ProfilingAgent,
|
profiler: &dyn ProfilingAgent,
|
||||||
) -> Result<Self, SetupError> {
|
) -> Result<Vec<Self>, SetupError> {
|
||||||
let artifacts = CompilationArtifacts::build(compiler, data)?;
|
maybe_parallel!(artifacts.(into_iter | into_par_iter))
|
||||||
Self::from_artifacts(artifacts, compiler.isa(), profiler)
|
.map(|a| CompiledModule::from_artifacts(a, isa, profiler))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates `CompiledModule` directly from `CompilationArtifacts`.
|
/// Creates `CompiledModule` directly from `CompilationArtifacts`.
|
||||||
@@ -174,67 +180,51 @@ impl CompiledModule {
|
|||||||
isa: &dyn TargetIsa,
|
isa: &dyn TargetIsa,
|
||||||
profiler: &dyn ProfilingAgent,
|
profiler: &dyn ProfilingAgent,
|
||||||
) -> Result<Self, SetupError> {
|
) -> Result<Self, SetupError> {
|
||||||
let CompilationArtifacts {
|
|
||||||
module,
|
|
||||||
obj,
|
|
||||||
unwind_info,
|
|
||||||
data_initializers,
|
|
||||||
funcs,
|
|
||||||
debug_info,
|
|
||||||
} = artifacts;
|
|
||||||
|
|
||||||
// Allocate all of the compiled functions into executable memory,
|
// Allocate all of the compiled functions into executable memory,
|
||||||
// copying over their contents.
|
// copying over their contents.
|
||||||
let (code_memory, code_range, finished_functions, trampolines) =
|
let (code_memory, code_range, finished_functions, trampolines) = build_code_memory(
|
||||||
build_code_memory(isa, &obj, &module, &unwind_info).map_err(|message| {
|
isa,
|
||||||
SetupError::Instantiate(InstantiationError::Resource(format!(
|
&artifacts.obj,
|
||||||
"failed to build code memory for functions: {}",
|
&artifacts.module,
|
||||||
message
|
&artifacts.unwind_info,
|
||||||
)))
|
)
|
||||||
})?;
|
.map_err(|message| {
|
||||||
|
SetupError::Instantiate(InstantiationError::Resource(format!(
|
||||||
|
"failed to build code memory for functions: {}",
|
||||||
|
message
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
// Register GDB JIT images; initialize profiler and load the wasm module.
|
// Register GDB JIT images; initialize profiler and load the wasm module.
|
||||||
let dbg_jit_registration = if debug_info {
|
let dbg_jit_registration = if artifacts.debug_info {
|
||||||
let bytes = create_dbg_image(obj.to_vec(), code_range, &module, &finished_functions)?;
|
let bytes = create_dbg_image(
|
||||||
|
artifacts.obj.to_vec(),
|
||||||
profiler.module_load(&module, &finished_functions, Some(&bytes));
|
code_range,
|
||||||
|
&artifacts.module,
|
||||||
|
&finished_functions,
|
||||||
|
)?;
|
||||||
|
profiler.module_load(&artifacts.module, &finished_functions, Some(&bytes));
|
||||||
let reg = GdbJitImageRegistration::register(bytes);
|
let reg = GdbJitImageRegistration::register(bytes);
|
||||||
Some(reg)
|
Some(reg)
|
||||||
} else {
|
} else {
|
||||||
profiler.module_load(&module, &finished_functions, None);
|
profiler.module_load(&artifacts.module, &finished_functions, None);
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let finished_functions = FinishedFunctions(finished_functions);
|
let finished_functions = FinishedFunctions(finished_functions);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
module: Arc::new(module),
|
module: Arc::new(artifacts.module.clone()),
|
||||||
|
artifacts,
|
||||||
code: Arc::new(ModuleCode {
|
code: Arc::new(ModuleCode {
|
||||||
code_memory,
|
code_memory,
|
||||||
dbg_jit_registration,
|
dbg_jit_registration,
|
||||||
}),
|
}),
|
||||||
finished_functions,
|
finished_functions,
|
||||||
trampolines,
|
trampolines,
|
||||||
data_initializers,
|
|
||||||
funcs,
|
|
||||||
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(),
|
|
||||||
funcs: self.funcs.clone(),
|
|
||||||
debug_info: self.code.dbg_jit_registration.is_some(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crate an `Instance` from this `CompiledModule`.
|
/// Crate an `Instance` from this `CompiledModule`.
|
||||||
///
|
///
|
||||||
/// Note that if only one instance of this module is needed, it may be more
|
/// Note that if only one instance of this module is needed, it may be more
|
||||||
@@ -267,10 +257,15 @@ impl CompiledModule {
|
|||||||
stack_map_registry,
|
stack_map_registry,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/// Extracts `CompilationArtifacts` from the compiled module.
|
||||||
|
pub fn compilation_artifacts(&self) -> &CompilationArtifacts {
|
||||||
|
&self.artifacts
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns data initializers to pass to `InstanceHandle::initialize`
|
/// Returns data initializers to pass to `InstanceHandle::initialize`
|
||||||
pub fn data_initializers(&self) -> Vec<DataInitializer<'_>> {
|
pub fn data_initializers(&self) -> Vec<DataInitializer<'_>> {
|
||||||
self.data_initializers
|
self.artifacts
|
||||||
|
.data_initializers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|init| DataInitializer {
|
.map(|init| DataInitializer {
|
||||||
location: init.location.clone(),
|
location: init.location.clone(),
|
||||||
@@ -307,10 +302,12 @@ impl CompiledModule {
|
|||||||
pub fn stack_maps(
|
pub fn stack_maps(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = (*mut [VMFunctionBody], &[StackMapInformation])> {
|
) -> impl Iterator<Item = (*mut [VMFunctionBody], &[StackMapInformation])> {
|
||||||
self.finished_functions()
|
self.finished_functions().values().copied().zip(
|
||||||
.values()
|
self.artifacts
|
||||||
.copied()
|
.funcs
|
||||||
.zip(self.funcs.values().map(|f| f.stack_maps.as_slice()))
|
.values()
|
||||||
|
.map(|f| f.stack_maps.as_slice()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterates over all functions in this module, returning information about
|
/// Iterates over all functions in this module, returning information about
|
||||||
@@ -327,7 +324,7 @@ impl CompiledModule {
|
|||||||
> {
|
> {
|
||||||
self.finished_functions()
|
self.finished_functions()
|
||||||
.iter()
|
.iter()
|
||||||
.zip(self.funcs.values())
|
.zip(self.artifacts.funcs.values())
|
||||||
.map(|((i, alloc), func)| (i, *alloc, func.traps.as_slice(), &func.address_map))
|
.map(|((i, alloc), func)| (i, *alloc, func.traps.as_slice(), &func.address_map))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,20 @@
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
#[cfg(feature = "parallel-compilation")]
|
||||||
|
macro_rules! maybe_parallel {
|
||||||
|
($e:ident.($serial:ident | $parallel:ident)) => {
|
||||||
|
$e.$parallel()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "parallel-compilation"))]
|
||||||
|
macro_rules! maybe_parallel {
|
||||||
|
($e:ident.($serial:ident | $parallel:ident)) => {
|
||||||
|
$e.$serial()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
mod code_memory;
|
mod code_memory;
|
||||||
mod compiler;
|
mod compiler;
|
||||||
mod instantiate;
|
mod instantiate;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use wasmtime_environ::wasm::{
|
|||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
entity::PrimaryMap, BuiltinFunctionIndex, CompileError, CompiledFunction, Compiler,
|
entity::PrimaryMap, BuiltinFunctionIndex, CompileError, CompiledFunction, Compiler,
|
||||||
FunctionBodyData, Module, ModuleTranslation, Relocation, RelocationTarget, TrapInformation,
|
FunctionBodyData, Module, ModuleTranslation, Relocation, RelocationTarget, TrapInformation,
|
||||||
VMOffsets,
|
Tunables, VMOffsets,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A compiler that compiles a WebAssembly module with Lightbeam, directly translating the Wasm file.
|
/// A compiler that compiles a WebAssembly module with Lightbeam, directly translating the Wasm file.
|
||||||
@@ -27,8 +27,9 @@ impl Compiler for Lightbeam {
|
|||||||
i: DefinedFuncIndex,
|
i: DefinedFuncIndex,
|
||||||
function_body: FunctionBodyData<'_>,
|
function_body: FunctionBodyData<'_>,
|
||||||
isa: &dyn isa::TargetIsa,
|
isa: &dyn isa::TargetIsa,
|
||||||
|
tunables: &Tunables,
|
||||||
) -> Result<CompiledFunction, CompileError> {
|
) -> Result<CompiledFunction, CompileError> {
|
||||||
if translation.tunables.debug_info {
|
if tunables.debug_info {
|
||||||
return Err(CompileError::DebugInfoNotSupported);
|
return Err(CompileError::DebugInfoNotSupported);
|
||||||
}
|
}
|
||||||
let func_index = translation.module.func_index(i);
|
let func_index = translation.module.func_index(i);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::frame_info::GlobalFrameInfoRegistration;
|
use crate::frame_info::GlobalFrameInfoRegistration;
|
||||||
use crate::runtime::{Config, Engine};
|
|
||||||
use crate::types::{EntityType, ExportType, ExternType, ImportType};
|
use crate::types::{EntityType, ExportType, ExternType, ImportType};
|
||||||
|
use crate::Engine;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
|
use std::hash::Hash;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use wasmparser::Validator;
|
use wasmparser::Validator;
|
||||||
@@ -81,7 +82,8 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule};
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
engine: Engine,
|
engine: Engine,
|
||||||
compiled: Arc<CompiledModule>,
|
compiled: Arc<[CompiledModule]>,
|
||||||
|
index: usize,
|
||||||
frame_info_registration: Arc<Mutex<Option<Option<Arc<GlobalFrameInfoRegistration>>>>>,
|
frame_info_registration: Arc<Mutex<Option<Option<Arc<GlobalFrameInfoRegistration>>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,8 +166,7 @@ impl Module {
|
|||||||
/// See [`Module::new`] for other details.
|
/// See [`Module::new`] for other details.
|
||||||
pub fn new_with_name(engine: &Engine, bytes: impl AsRef<[u8]>, name: &str) -> Result<Module> {
|
pub fn new_with_name(engine: &Engine, bytes: impl AsRef<[u8]>, name: &str) -> Result<Module> {
|
||||||
let mut module = Module::new(engine, bytes.as_ref())?;
|
let mut module = Module::new(engine, bytes.as_ref())?;
|
||||||
Arc::get_mut(&mut module.compiled)
|
Arc::get_mut(&mut module.compiled).unwrap()[module.index]
|
||||||
.unwrap()
|
|
||||||
.module_mut()
|
.module_mut()
|
||||||
.expect("mutable module")
|
.expect("mutable module")
|
||||||
.name = Some(name.to_string());
|
.name = Some(name.to_string());
|
||||||
@@ -248,7 +249,7 @@ impl Module {
|
|||||||
#[cfg(not(feature = "cache"))]
|
#[cfg(not(feature = "cache"))]
|
||||||
let artifacts = CompilationArtifacts::build(engine.compiler(), binary)?;
|
let artifacts = CompilationArtifacts::build(engine.compiler(), binary)?;
|
||||||
|
|
||||||
let compiled = CompiledModule::from_artifacts(
|
let compiled = CompiledModule::from_artifacts_list(
|
||||||
artifacts,
|
artifacts,
|
||||||
engine.compiler().isa(),
|
engine.compiler().isa(),
|
||||||
&*engine.config().profiler,
|
&*engine.config().profiler,
|
||||||
@@ -256,7 +257,8 @@ impl Module {
|
|||||||
|
|
||||||
Ok(Module {
|
Ok(Module {
|
||||||
engine: engine.clone(),
|
engine: engine.clone(),
|
||||||
compiled: Arc::new(compiled),
|
index: compiled.len() - 1,
|
||||||
|
compiled: compiled.into(),
|
||||||
frame_info_registration: Arc::new(Mutex::new(None)),
|
frame_info_registration: Arc::new(Mutex::new(None)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -290,8 +292,12 @@ impl Module {
|
|||||||
/// Serialize compilation artifacts to the buffer. See also `deseriaize`.
|
/// Serialize compilation artifacts to the buffer. See also `deseriaize`.
|
||||||
pub fn serialize(&self) -> Result<Vec<u8>> {
|
pub fn serialize(&self) -> Result<Vec<u8>> {
|
||||||
let artifacts = (
|
let artifacts = (
|
||||||
compiler_fingerprint(self.engine.config()),
|
compiler_fingerprint(&self.engine),
|
||||||
self.compiled.to_compilation_artifacts(),
|
self.compiled
|
||||||
|
.iter()
|
||||||
|
.map(|i| i.compilation_artifacts())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
self.index,
|
||||||
);
|
);
|
||||||
|
|
||||||
let buffer = bincode_options().serialize(&artifacts)?;
|
let buffer = bincode_options().serialize(&artifacts)?;
|
||||||
@@ -308,16 +314,16 @@ impl Module {
|
|||||||
/// for modifications or curruptions. All responsibily of signing and its
|
/// for modifications or curruptions. All responsibily of signing and its
|
||||||
/// verification falls on the embedder.
|
/// verification falls on the embedder.
|
||||||
pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result<Module> {
|
pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result<Module> {
|
||||||
let expected_fingerprint = compiler_fingerprint(engine.config());
|
let expected_fingerprint = compiler_fingerprint(engine);
|
||||||
|
|
||||||
let (fingerprint, artifacts) = bincode_options()
|
let (fingerprint, artifacts, index) = bincode_options()
|
||||||
.deserialize::<(u64, CompilationArtifacts)>(serialized)
|
.deserialize::<(u64, _, _)>(serialized)
|
||||||
.context("Deserialize compilation artifacts")?;
|
.context("Deserialize compilation artifacts")?;
|
||||||
if fingerprint != expected_fingerprint {
|
if fingerprint != expected_fingerprint {
|
||||||
bail!("Incompatible compilation artifact");
|
bail!("Incompatible compilation artifact");
|
||||||
}
|
}
|
||||||
|
|
||||||
let compiled = CompiledModule::from_artifacts(
|
let compiled = CompiledModule::from_artifacts_list(
|
||||||
artifacts,
|
artifacts,
|
||||||
engine.compiler().isa(),
|
engine.compiler().isa(),
|
||||||
&*engine.config().profiler,
|
&*engine.config().profiler,
|
||||||
@@ -325,13 +331,14 @@ impl Module {
|
|||||||
|
|
||||||
Ok(Module {
|
Ok(Module {
|
||||||
engine: engine.clone(),
|
engine: engine.clone(),
|
||||||
compiled: Arc::new(compiled),
|
index,
|
||||||
|
compiled: compiled.into(),
|
||||||
frame_info_registration: Arc::new(Mutex::new(None)),
|
frame_info_registration: Arc::new(Mutex::new(None)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compiled_module(&self) -> &CompiledModule {
|
pub(crate) fn compiled_module(&self) -> &CompiledModule {
|
||||||
&self.compiled
|
&self.compiled[self.index]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns identifier/name that this [`Module`] has. This name
|
/// Returns identifier/name that this [`Module`] has. This name
|
||||||
@@ -359,7 +366,7 @@ impl Module {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
self.compiled.module().name.as_deref()
|
self.compiled_module().module().name.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the list of imports that this [`Module`] has and must be
|
/// Returns the list of imports that this [`Module`] has and must be
|
||||||
@@ -414,7 +421,7 @@ impl Module {
|
|||||||
pub fn imports<'module>(
|
pub fn imports<'module>(
|
||||||
&'module self,
|
&'module self,
|
||||||
) -> impl ExactSizeIterator<Item = ImportType<'module>> + 'module {
|
) -> impl ExactSizeIterator<Item = ImportType<'module>> + 'module {
|
||||||
let module = self.compiled.module();
|
let module = self.compiled_module().module();
|
||||||
module
|
module
|
||||||
.imports
|
.imports
|
||||||
.iter()
|
.iter()
|
||||||
@@ -481,7 +488,7 @@ impl Module {
|
|||||||
pub fn exports<'module>(
|
pub fn exports<'module>(
|
||||||
&'module self,
|
&'module self,
|
||||||
) -> impl ExactSizeIterator<Item = ExportType<'module>> + 'module {
|
) -> impl ExactSizeIterator<Item = ExportType<'module>> + 'module {
|
||||||
let module = self.compiled.module();
|
let module = self.compiled_module().module();
|
||||||
module.exports.iter().map(move |(name, entity_index)| {
|
module.exports.iter().map(move |(name, entity_index)| {
|
||||||
let r#type = EntityType::new(entity_index, module);
|
let r#type = EntityType::new(entity_index, module);
|
||||||
ExportType::new(name, r#type)
|
ExportType::new(name, r#type)
|
||||||
@@ -532,7 +539,7 @@ impl Module {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_export<'module>(&'module self, name: &'module str) -> Option<ExternType> {
|
pub fn get_export<'module>(&'module self, name: &'module str) -> Option<ExternType> {
|
||||||
let module = self.compiled.module();
|
let module = self.compiled_module().module();
|
||||||
let entity_index = module.exports.get(name)?;
|
let entity_index = module.exports.get(name)?;
|
||||||
Some(EntityType::new(entity_index, module).extern_type())
|
Some(EntityType::new(entity_index, module).extern_type())
|
||||||
}
|
}
|
||||||
@@ -550,7 +557,7 @@ impl Module {
|
|||||||
if let Some(info) = &*info {
|
if let Some(info) = &*info {
|
||||||
return info.clone();
|
return info.clone();
|
||||||
}
|
}
|
||||||
let ret = super::frame_info::register(&self.compiled).map(Arc::new);
|
let ret = super::frame_info::register(self.compiled_module()).map(Arc::new);
|
||||||
*info = Some(ret.clone());
|
*info = Some(ret.clone());
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -567,10 +574,10 @@ fn bincode_options() -> impl Options {
|
|||||||
bincode::DefaultOptions::new().with_varint_encoding()
|
bincode::DefaultOptions::new().with_varint_encoding()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compiler_fingerprint(config: &Config) -> u64 {
|
fn compiler_fingerprint(engine: &Engine) -> u64 {
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
config.compiler_fingerprint(&mut hasher);
|
engine.compiler().hash(&mut hasher);
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use std::hash::{Hash, Hasher};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use target_lexicon::Triple;
|
|
||||||
use wasmparser::WasmFeatures;
|
use wasmparser::WasmFeatures;
|
||||||
#[cfg(feature = "cache")]
|
#[cfg(feature = "cache")]
|
||||||
use wasmtime_cache::CacheConfig;
|
use wasmtime_cache::CacheConfig;
|
||||||
@@ -257,6 +256,20 @@ impl Config {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configures whether the WebAssembly module linking [proposal] will
|
||||||
|
/// be enabled for compilation.
|
||||||
|
///
|
||||||
|
/// Note that development of this feature is still underway, so enabling
|
||||||
|
/// this is likely to be full of bugs.
|
||||||
|
///
|
||||||
|
/// This is `false` by default.
|
||||||
|
///
|
||||||
|
/// [proposal]: https://github.com/webassembly/module-linking
|
||||||
|
pub fn wasm_module_linking(&mut self, enable: bool) -> &mut Self {
|
||||||
|
self.features.module_linking = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Configures which compilation strategy will be used for wasm modules.
|
/// Configures which compilation strategy will be used for wasm modules.
|
||||||
///
|
///
|
||||||
/// This method can be used to configure which compiler is used for wasm
|
/// This method can be used to configure which compiler is used for wasm
|
||||||
@@ -616,22 +629,6 @@ impl Config {
|
|||||||
let isa = self.target_isa();
|
let isa = self.target_isa();
|
||||||
Compiler::new(isa, self.strategy, self.tunables.clone(), self.features)
|
Compiler::new(isa, self.strategy, self.tunables.clone(), self.features)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 {
|
fn round_up_to_pages(val: u64) -> u64 {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ pub fn compile_to_obj(
|
|||||||
let mut translation = environ
|
let mut translation = environ
|
||||||
.translate(wasm)
|
.translate(wasm)
|
||||||
.context("failed to translate module")?;
|
.context("failed to translate module")?;
|
||||||
let compilation = compiler.compile(&mut translation)?;
|
assert_eq!(translation.len(), 1);
|
||||||
|
let compilation = compiler.compile(&mut translation[0])?;
|
||||||
Ok(compilation.obj)
|
Ok(compilation.obj)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ fn loop_interrupt_from_afar() -> anyhow::Result<()> {
|
|||||||
while HITS.load(SeqCst) <= 100_000 {
|
while HITS.load(SeqCst) <= 100_000 {
|
||||||
// continue ...
|
// continue ...
|
||||||
}
|
}
|
||||||
|
println!("interrupting");
|
||||||
handle.interrupt();
|
handle.interrupt();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ mod instance;
|
|||||||
mod invoke_func_via_table;
|
mod invoke_func_via_table;
|
||||||
mod linker;
|
mod linker;
|
||||||
mod memory_creator;
|
mod memory_creator;
|
||||||
|
mod module_linking;
|
||||||
mod module_serialize;
|
mod module_serialize;
|
||||||
mod name;
|
mod name;
|
||||||
mod stack_overflow;
|
mod stack_overflow;
|
||||||
|
|||||||
46
tests/all/module_linking.rs
Normal file
46
tests/all/module_linking.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use wasmtime::*;
|
||||||
|
|
||||||
|
fn engine() -> Engine {
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.wasm_module_linking(true);
|
||||||
|
Engine::new(&config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile() -> Result<()> {
|
||||||
|
let engine = engine();
|
||||||
|
Module::new(&engine, "(module (module))")?;
|
||||||
|
Module::new(&engine, "(module (module) (module))")?;
|
||||||
|
Module::new(&engine, "(module (module (module)))")?;
|
||||||
|
Module::new(
|
||||||
|
&engine,
|
||||||
|
"
|
||||||
|
(module
|
||||||
|
(func)
|
||||||
|
(module (func))
|
||||||
|
(module (func))
|
||||||
|
)
|
||||||
|
",
|
||||||
|
)?;
|
||||||
|
let m = Module::new(
|
||||||
|
&engine,
|
||||||
|
"
|
||||||
|
(module
|
||||||
|
(global i32 (i32.const 0))
|
||||||
|
(func)
|
||||||
|
(module (memory 1) (func))
|
||||||
|
(module (memory 2) (func))
|
||||||
|
(module (table 2 funcref) (func))
|
||||||
|
(module (global i64 (i64.const 0)) (func))
|
||||||
|
)
|
||||||
|
",
|
||||||
|
)?;
|
||||||
|
assert_eq!(m.imports().len(), 0);
|
||||||
|
assert_eq!(m.exports().len(), 0);
|
||||||
|
let bytes = m.serialize()?;
|
||||||
|
Module::deserialize(&engine, &bytes)?;
|
||||||
|
assert_eq!(m.imports().len(), 0);
|
||||||
|
assert_eq!(m.exports().len(), 0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user