This is enough to get an `externref -> externref` identity function passing. However, `externref`s that are dropped by compiled Wasm code are (safely) leaked. Follow up work will leverage cranelift's stack maps to resolve this issue.
480 lines
15 KiB
Rust
480 lines
15 KiB
Rust
use crate::module::{EntityIndex, MemoryPlan, Module, TableElements, TablePlan};
|
|
use crate::tunables::Tunables;
|
|
use cranelift_codegen::ir;
|
|
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
|
|
use cranelift_codegen::isa::TargetFrontendConfig;
|
|
use cranelift_entity::PrimaryMap;
|
|
use cranelift_wasm::{
|
|
self, translate_module, DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex,
|
|
Memory, MemoryIndex, ModuleTranslationState, SignatureIndex, Table, TableIndex,
|
|
TargetEnvironment, WasmError, WasmFuncType, WasmResult,
|
|
};
|
|
use std::convert::TryFrom;
|
|
use std::sync::Arc;
|
|
|
|
/// 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 result of translating via `ModuleEnvironment`. Function bodies are not
|
|
/// yet translated, and data initializers have not yet been copied out of the
|
|
/// original buffer.
|
|
pub struct ModuleTranslation<'data> {
|
|
/// Compilation setting flags.
|
|
pub target_config: TargetFrontendConfig,
|
|
|
|
/// Module information.
|
|
pub module: Module,
|
|
|
|
/// References to the function bodies.
|
|
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'data>>,
|
|
|
|
/// References to the data initializers.
|
|
pub data_initializers: Vec<DataInitializer<'data>>,
|
|
|
|
/// Tunable parameters.
|
|
pub tunables: Tunables,
|
|
|
|
/// The decoded Wasm types for the module.
|
|
pub module_translation: Option<ModuleTranslationState>,
|
|
}
|
|
|
|
/// Object containing the standalone environment information.
|
|
pub struct ModuleEnvironment<'data> {
|
|
/// The result to be filled in.
|
|
result: ModuleTranslation<'data>,
|
|
}
|
|
|
|
impl<'data> ModuleEnvironment<'data> {
|
|
/// Allocates the environment data structures.
|
|
pub fn new(target_config: TargetFrontendConfig, tunables: &Tunables) -> Self {
|
|
Self {
|
|
result: ModuleTranslation {
|
|
target_config,
|
|
module: Module::new(),
|
|
function_body_inputs: PrimaryMap::new(),
|
|
data_initializers: Vec::new(),
|
|
tunables: tunables.clone(),
|
|
module_translation: None,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn pointer_type(&self) -> ir::Type {
|
|
self.result.target_config.pointer_type()
|
|
}
|
|
|
|
/// Translate a wasm module using this environment. This consumes the
|
|
/// `ModuleEnvironment` and produces a `ModuleTranslation`.
|
|
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<ModuleTranslation<'data>> {
|
|
assert!(self.result.module_translation.is_none());
|
|
let module_translation = translate_module(data, &mut self)?;
|
|
self.result.module_translation = Some(module_translation);
|
|
Ok(self.result)
|
|
}
|
|
|
|
fn declare_export(&mut self, export: EntityIndex, name: &str) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.exports
|
|
.insert(String::from(name), export);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<'data> TargetEnvironment for ModuleEnvironment<'data> {
|
|
fn target_config(&self) -> TargetFrontendConfig {
|
|
self.result.target_config
|
|
}
|
|
|
|
fn reference_type(&self) -> ir::Type {
|
|
// For now, the only reference types we support are `externref`, which
|
|
// don't require tracing GC and stack maps. So we just use the target's
|
|
// pointer type. This will have to change once we move to tracing GC.
|
|
self.pointer_type()
|
|
}
|
|
}
|
|
|
|
/// This trait is useful for `translate_module` because it tells how to translate
|
|
/// environment-dependent wasm instructions. These functions should not be called by the user.
|
|
impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data> {
|
|
fn reserve_signatures(&mut self, num: u32) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.local
|
|
.signatures
|
|
.reserve_exact(usize::try_from(num).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_signature(&mut self, wasm: &WasmFuncType, sig: ir::Signature) -> WasmResult<()> {
|
|
let sig = translate_signature(sig, self.pointer_type());
|
|
// TODO: Deduplicate signatures.
|
|
self.result
|
|
.module
|
|
.local
|
|
.signatures
|
|
.push((wasm.clone(), sig));
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_imports(&mut self, num: u32) -> WasmResult<()> {
|
|
Ok(self
|
|
.result
|
|
.module
|
|
.imports
|
|
.reserve_exact(usize::try_from(num).unwrap()))
|
|
}
|
|
|
|
fn declare_func_import(
|
|
&mut self,
|
|
sig_index: SignatureIndex,
|
|
module: &str,
|
|
field: &str,
|
|
) -> WasmResult<()> {
|
|
debug_assert_eq!(
|
|
self.result.module.local.functions.len(),
|
|
self.result.module.local.num_imported_funcs,
|
|
"Imported functions must be declared first"
|
|
);
|
|
let func_index = self.result.module.local.functions.push(sig_index);
|
|
self.result.module.imports.push((
|
|
module.to_owned(),
|
|
field.to_owned(),
|
|
EntityIndex::Function(func_index),
|
|
));
|
|
self.result.module.local.num_imported_funcs += 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_table_import(&mut self, table: Table, module: &str, field: &str) -> WasmResult<()> {
|
|
debug_assert_eq!(
|
|
self.result.module.local.table_plans.len(),
|
|
self.result.module.local.num_imported_tables,
|
|
"Imported tables must be declared first"
|
|
);
|
|
let plan = TablePlan::for_table(table, &self.result.tunables);
|
|
let table_index = self.result.module.local.table_plans.push(plan);
|
|
self.result.module.imports.push((
|
|
module.to_owned(),
|
|
field.to_owned(),
|
|
EntityIndex::Table(table_index),
|
|
));
|
|
self.result.module.local.num_imported_tables += 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_memory_import(
|
|
&mut self,
|
|
memory: Memory,
|
|
module: &str,
|
|
field: &str,
|
|
) -> WasmResult<()> {
|
|
debug_assert_eq!(
|
|
self.result.module.local.memory_plans.len(),
|
|
self.result.module.local.num_imported_memories,
|
|
"Imported memories must be declared first"
|
|
);
|
|
if memory.shared {
|
|
return Err(WasmError::Unsupported("shared memories".to_owned()));
|
|
}
|
|
let plan = MemoryPlan::for_memory(memory, &self.result.tunables);
|
|
let memory_index = self.result.module.local.memory_plans.push(plan);
|
|
self.result.module.imports.push((
|
|
module.to_owned(),
|
|
field.to_owned(),
|
|
EntityIndex::Memory(memory_index),
|
|
));
|
|
self.result.module.local.num_imported_memories += 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_global_import(
|
|
&mut self,
|
|
global: Global,
|
|
module: &str,
|
|
field: &str,
|
|
) -> WasmResult<()> {
|
|
debug_assert_eq!(
|
|
self.result.module.local.globals.len(),
|
|
self.result.module.local.num_imported_globals,
|
|
"Imported globals must be declared first"
|
|
);
|
|
let global_index = self.result.module.local.globals.push(global);
|
|
self.result.module.imports.push((
|
|
module.to_owned(),
|
|
field.to_owned(),
|
|
EntityIndex::Global(global_index),
|
|
));
|
|
self.result.module.local.num_imported_globals += 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_func_types(&mut self, num: u32) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.local
|
|
.functions
|
|
.reserve_exact(usize::try_from(num).unwrap());
|
|
self.result
|
|
.function_body_inputs
|
|
.reserve_exact(usize::try_from(num).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_func_type(&mut self, sig_index: SignatureIndex) -> WasmResult<()> {
|
|
self.result.module.local.functions.push(sig_index);
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_tables(&mut self, num: u32) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.local
|
|
.table_plans
|
|
.reserve_exact(usize::try_from(num).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_table(&mut self, table: Table) -> WasmResult<()> {
|
|
let plan = TablePlan::for_table(table, &self.result.tunables);
|
|
self.result.module.local.table_plans.push(plan);
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_memories(&mut self, num: u32) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.local
|
|
.memory_plans
|
|
.reserve_exact(usize::try_from(num).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_memory(&mut self, memory: Memory) -> WasmResult<()> {
|
|
if memory.shared {
|
|
return Err(WasmError::Unsupported("shared memories".to_owned()));
|
|
}
|
|
let plan = MemoryPlan::for_memory(memory, &self.result.tunables);
|
|
self.result.module.local.memory_plans.push(plan);
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_globals(&mut self, num: u32) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.local
|
|
.globals
|
|
.reserve_exact(usize::try_from(num).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_global(&mut self, global: Global) -> WasmResult<()> {
|
|
self.result.module.local.globals.push(global);
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_exports(&mut self, num: u32) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.exports
|
|
.reserve(usize::try_from(num).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_func_export(&mut self, func_index: FuncIndex, name: &str) -> WasmResult<()> {
|
|
self.declare_export(EntityIndex::Function(func_index), name)
|
|
}
|
|
|
|
fn declare_table_export(&mut self, table_index: TableIndex, name: &str) -> WasmResult<()> {
|
|
self.declare_export(EntityIndex::Table(table_index), name)
|
|
}
|
|
|
|
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) -> WasmResult<()> {
|
|
self.declare_export(EntityIndex::Memory(memory_index), name)
|
|
}
|
|
|
|
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) -> WasmResult<()> {
|
|
self.declare_export(EntityIndex::Global(global_index), name)
|
|
}
|
|
|
|
fn declare_start_func(&mut self, func_index: FuncIndex) -> WasmResult<()> {
|
|
debug_assert!(self.result.module.start_func.is_none());
|
|
self.result.module.start_func = Some(func_index);
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_table_elements(&mut self, num: u32) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.table_elements
|
|
.reserve_exact(usize::try_from(num).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_table_elements(
|
|
&mut self,
|
|
table_index: TableIndex,
|
|
base: Option<GlobalIndex>,
|
|
offset: usize,
|
|
elements: Box<[FuncIndex]>,
|
|
) -> WasmResult<()> {
|
|
self.result.module.table_elements.push(TableElements {
|
|
table_index,
|
|
base,
|
|
offset,
|
|
elements,
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_passive_element(
|
|
&mut self,
|
|
elem_index: ElemIndex,
|
|
segments: Box<[FuncIndex]>,
|
|
) -> WasmResult<()> {
|
|
let old = self
|
|
.result
|
|
.module
|
|
.passive_elements
|
|
.insert(elem_index, segments);
|
|
debug_assert!(
|
|
old.is_none(),
|
|
"should never get duplicate element indices, that would be a bug in `cranelift_wasm`'s \
|
|
translation"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn define_function_body(
|
|
&mut self,
|
|
_module_translation: &ModuleTranslationState,
|
|
body_bytes: &'data [u8],
|
|
body_offset: usize,
|
|
) -> WasmResult<()> {
|
|
self.result.function_body_inputs.push(FunctionBodyData {
|
|
data: body_bytes,
|
|
module_offset: body_offset,
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_data_initializers(&mut self, num: u32) -> WasmResult<()> {
|
|
self.result
|
|
.data_initializers
|
|
.reserve_exact(usize::try_from(num).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_data_initialization(
|
|
&mut self,
|
|
memory_index: MemoryIndex,
|
|
base: Option<GlobalIndex>,
|
|
offset: usize,
|
|
data: &'data [u8],
|
|
) -> WasmResult<()> {
|
|
self.result.data_initializers.push(DataInitializer {
|
|
location: DataInitializerLocation {
|
|
memory_index,
|
|
base,
|
|
offset,
|
|
},
|
|
data,
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_passive_data(&mut self, count: u32) -> WasmResult<()> {
|
|
self.result.module.passive_data.reserve(count as usize);
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_passive_data(&mut self, data_index: DataIndex, data: &'data [u8]) -> WasmResult<()> {
|
|
let old = self
|
|
.result
|
|
.module
|
|
.passive_data
|
|
.insert(data_index, Arc::from(data));
|
|
debug_assert!(
|
|
old.is_none(),
|
|
"a module can't have duplicate indices, this would be a cranelift-wasm bug"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_module_name(&mut self, name: &'data str) -> WasmResult<()> {
|
|
self.result.module.name = Some(name.to_string());
|
|
Ok(())
|
|
}
|
|
|
|
fn declare_func_name(&mut self, func_index: FuncIndex, name: &'data str) -> WasmResult<()> {
|
|
self.result
|
|
.module
|
|
.func_names
|
|
.insert(func_index, name.to_string());
|
|
Ok(())
|
|
}
|
|
|
|
fn custom_section(&mut self, name: &'data str, _data: &'data [u8]) -> WasmResult<()> {
|
|
match name {
|
|
"webidl-bindings" | "wasm-interface-types" => Err(WasmError::Unsupported(
|
|
"\
|
|
Support for interface types has temporarily been removed from `wasmtime`.
|
|
|
|
For more information about this temoprary you can read on the issue online:
|
|
|
|
https://github.com/bytecodealliance/wasmtime/issues/1271
|
|
|
|
and for re-adding support for interface types you can see this issue:
|
|
|
|
https://github.com/bytecodealliance/wasmtime/issues/677
|
|
"
|
|
.to_owned(),
|
|
)),
|
|
// skip other sections
|
|
_ => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Add environment-specific function parameters.
|
|
pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir::Signature {
|
|
// Prepend the vmctx argument.
|
|
sig.params.insert(
|
|
0,
|
|
AbiParam::special(pointer_type, ArgumentPurpose::VMContext),
|
|
);
|
|
// Prepend the caller vmctx argument.
|
|
sig.params.insert(1, AbiParam::new(pointer_type));
|
|
sig
|
|
}
|
|
|
|
/// A memory index and offset within that memory where a data initialization
|
|
/// should is to be performed.
|
|
#[derive(Clone)]
|
|
pub struct DataInitializerLocation {
|
|
/// The index of the memory to initialize.
|
|
pub memory_index: MemoryIndex,
|
|
|
|
/// Optionally a globalvar base to initialize at.
|
|
pub base: Option<GlobalIndex>,
|
|
|
|
/// A constant offset to initialize at.
|
|
pub offset: usize,
|
|
}
|
|
|
|
/// A data initializer for linear memory.
|
|
pub struct DataInitializer<'data> {
|
|
/// The location where the initialization is to be performed.
|
|
pub location: DataInitializerLocation,
|
|
|
|
/// The initialization data.
|
|
pub data: &'data [u8],
|
|
}
|