Files
wasmtime/lib/runtime/src/lib.rs
Dan Gohman 9d41135fe3 Translate from function-body index to function-index-space index.
The wasm function index space consists of the index space of the
imported functions concatenated with the index space of the defined
functions. When iterating through function definitions, it's necessary
to adjust the index when a function-index-space index is needed.
2018-05-17 20:39:19 -07:00

597 lines
20 KiB
Rust

//! Standalone runtime for WebAssembly using Cretonne. Provides functions to translate
//! `get_global`, `set_global`, `current_memory`, `grow_memory`, `call_indirect` that hardcode in
//! the translation the base addresses of regions of memory that will hold the globals, tables and
//! linear memories.
#![deny(missing_docs)]
extern crate cretonne_codegen;
extern crate cretonne_wasm;
extern crate wasmparser;
pub mod module;
pub mod compilation;
pub mod instance;
pub use module::Module;
pub use compilation::Compilation;
pub use instance::Instance;
use cretonne_wasm::{FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, Global, Table, Memory,
GlobalValue, SignatureIndex, FuncTranslator};
use cretonne_codegen::ir::{InstBuilder, FuncRef, ExtFuncData, ExternalName, Signature, AbiParam,
ArgumentPurpose, ArgumentLoc, ArgumentExtension, Function};
use cretonne_codegen::ir::types::*;
use cretonne_codegen::ir::immediates::Offset32;
use cretonne_codegen::cursor::FuncCursor;
use cretonne_codegen::ir;
use cretonne_codegen::isa;
use cretonne_codegen::settings;
use cretonne_codegen::binemit;
/// Compute a `ir::ExternalName` for a given wasm function index.
pub fn get_func_name(func_index: FunctionIndex) -> cretonne_codegen::ir::ExternalName {
debug_assert!(func_index as u32 as FunctionIndex == func_index);
ir::ExternalName::user(0, func_index as u32)
}
/// An entity to export.
pub enum Export {
/// Function export.
Function(FunctionIndex),
/// Table export.
Table(TableIndex),
/// Memory export.
Memory(MemoryIndex),
/// Global export.
Global(GlobalIndex),
}
/// Implementation of a relocation sink that just saves all the information for later
pub struct RelocSink {
/// Relocations recorded for the function.
pub func_relocs: Vec<Relocation>,
}
impl binemit::RelocSink for RelocSink {
fn reloc_ebb(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_ebb_offset: binemit::CodeOffset,
) {
// This should use the `offsets` field of `ir::Function`.
panic!("ebb headers not yet implemented");
}
fn reloc_external(
&mut self,
offset: binemit::CodeOffset,
reloc: binemit::Reloc,
name: &ExternalName,
addend: binemit::Addend,
) {
// FIXME: Handle grow_memory/current_memory.
let func_index = if let ExternalName::User { namespace, index } = *name {
debug_assert!(namespace == 0);
index
} else {
panic!("unrecognized external name")
} as usize;
self.func_relocs.push(Relocation {
reloc,
func_index,
offset,
addend,
});
}
fn reloc_jt(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_jt: ir::JumpTable,
) {
panic!("jump tables not yet implemented");
}
}
impl RelocSink {
fn new() -> RelocSink {
RelocSink { func_relocs: Vec::new() }
}
}
/// A data initializer for linear memory.
pub struct DataInitializer<'data> {
/// 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,
/// The initialization data.
pub data: &'data [u8],
}
/// References to the input wasm data buffer to be decoded and processed later.
/// separately from the main module translation.
pub struct LazyContents<'data> {
/// References to the function bodies.
pub function_body_inputs: Vec<&'data [u8]>,
/// References to the data initializers.
pub data_initializers: Vec<DataInitializer<'data>>,
}
impl<'data> LazyContents<'data> {
fn new() -> Self {
Self {
function_body_inputs: Vec::new(),
data_initializers: Vec::new(),
}
}
}
/// Object containing the standalone runtime information. To be passed after creation as argument
/// to `cretonne_wasm::translatemodule`.
pub struct ModuleEnvironment<'data, 'module> {
/// Compilation setting flags.
pub settings_flags: &'module settings::Flags,
/// Module information.
pub module: &'module mut Module,
/// References to information to be decoded later.
pub lazy: LazyContents<'data>,
}
impl<'data, 'module> ModuleEnvironment<'data, 'module> {
/// Allocates the runtime data structures with the given isa.
pub fn new(flags: &'module settings::Flags, module: &'module mut Module) -> Self {
Self {
settings_flags: flags,
module,
lazy: LazyContents::new(),
}
}
fn func_env(&self) -> FuncEnvironment {
FuncEnvironment::new(&self.settings_flags, &self.module)
}
fn native_pointer(&self) -> ir::Type {
use cretonne_wasm::FuncEnvironment;
self.func_env().native_pointer()
}
/// Declare that translation of the module is complete. This consumes the
/// `ModuleEnvironment` with its mutable reference to the `Module` and
/// produces a `ModuleTranslation` with an immutable reference to the
/// `Module`.
pub fn finish_translation(self) -> ModuleTranslation<'data, 'module> {
ModuleTranslation {
flags: self.settings_flags,
module: self.module,
lazy: self.lazy,
}
}
}
/// The FuncEnvironment implementation for use by the `ModuleEnvironment`.
pub struct FuncEnvironment<'module_environment> {
/// Compilation setting flags.
settings_flags: &'module_environment settings::Flags,
/// The module-level environment which this function-level environment belongs to.
pub module: &'module_environment Module,
/// The Cretonne global holding the base address of the memories vector.
pub memories_base: Option<ir::GlobalVar>,
/// The Cretonne global holding the base address of the globals vector.
pub globals_base: Option<ir::GlobalVar>,
/// The external function declaration for implementing wasm's `current_memory`.
pub current_memory_extfunc: Option<FuncRef>,
/// The external function declaration for implementing wasm's `grow_memory`.
pub grow_memory_extfunc: Option<FuncRef>,
}
impl<'module_environment> FuncEnvironment<'module_environment> {
fn new(
flags: &'module_environment settings::Flags,
module: &'module_environment Module,
) -> Self {
Self {
settings_flags: flags,
module,
memories_base: None,
globals_base: None,
current_memory_extfunc: None,
grow_memory_extfunc: None,
}
}
/// Transform the call argument list in preparation for making a call.
fn get_real_call_args(func: &Function, call_args: &[ir::Value]) -> Vec<ir::Value> {
let mut real_call_args = Vec::with_capacity(call_args.len() + 1);
real_call_args.extend_from_slice(call_args);
real_call_args.push(func.special_param(ArgumentPurpose::VMContext).unwrap());
real_call_args
}
fn ptr_size(&self) -> usize {
if self.settings_flags.is_64bit() { 8 } else { 4 }
}
}
impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
fn flags(&self) -> &settings::Flags {
&self.settings_flags
}
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue {
let ptr_size = self.ptr_size();
let globals_base = self.globals_base.unwrap_or_else(|| {
let offset = 0 * ptr_size;
let offset32 = offset as i32;
debug_assert_eq!(offset32 as usize, offset);
let new_base = func.create_global_var(ir::GlobalVarData::VMContext {
offset: Offset32::new(offset32),
});
self.globals_base = Some(new_base);
new_base
});
let offset = index as usize * 8;
let offset32 = offset as i32;
debug_assert_eq!(offset32 as usize, offset);
let gv = func.create_global_var(ir::GlobalVarData::Deref {
base: globals_base,
offset: Offset32::new(offset32),
});
GlobalValue::Memory {
gv,
ty: self.module.globals[index].ty,
}
}
fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> ir::Heap {
let ptr_size = self.ptr_size();
let memories_base = self.memories_base.unwrap_or_else(|| {
let new_base = func.create_global_var(ir::GlobalVarData::VMContext {
offset: Offset32::new(ptr_size as i32),
});
self.globals_base = Some(new_base);
new_base
});
let offset = index as usize * ptr_size;
let offset32 = offset as i32;
debug_assert_eq!(offset32 as usize, offset);
let heap_base = func.create_global_var(ir::GlobalVarData::Deref {
base: memories_base,
offset: Offset32::new(offset32),
});
let h = func.create_heap(ir::HeapData {
base: ir::HeapBase::GlobalVar(heap_base),
min_size: 0.into(),
guard_size: 0x8000_0000.into(),
style: ir::HeapStyle::Static { bound: 0x1_0000_0000.into() },
});
h
}
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef {
func.import_signature(self.module.signatures[index].clone())
}
fn make_direct_func(&mut self, func: &mut ir::Function, index: FunctionIndex) -> ir::FuncRef {
let sigidx = self.module.functions[index];
let signature = func.import_signature(self.module.signatures[sigidx].clone());
let name = get_func_name(index);
// We currently allocate all code segments independently, so nothing
// is colocated.
let colocated = false;
func.import_function(ir::ExtFuncData {
name,
signature,
colocated,
})
}
fn translate_call_indirect(
&mut self,
mut pos: FuncCursor,
table_index: TableIndex,
_sig_index: SignatureIndex,
sig_ref: ir::SigRef,
callee: ir::Value,
call_args: &[ir::Value],
) -> ir::Inst {
// TODO: Cretonne's call_indirect doesn't implement bounds checking
// or signature checking, so we need to implement it ourselves.
debug_assert_eq!(table_index, 0, "non-default tables not supported yet");
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
pos.ins().call_indirect(sig_ref, callee, &real_call_args)
}
fn translate_call(
&mut self,
mut pos: FuncCursor,
_callee_index: FunctionIndex,
callee: ir::FuncRef,
call_args: &[ir::Value],
) -> ir::Inst {
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
pos.ins().call(callee, &real_call_args)
}
fn translate_grow_memory(
&mut self,
mut pos: FuncCursor,
index: MemoryIndex,
_heap: ir::Heap,
val: ir::Value,
) -> ir::Value {
debug_assert_eq!(index, 0, "non-default memories not supported yet");
let grow_mem_func = self.grow_memory_extfunc.unwrap_or_else(|| {
let sig_ref = pos.func.import_signature(Signature {
call_conv: self.settings_flags.call_conv(),
argument_bytes: None,
params: vec![AbiParam::new(I32)],
returns: vec![AbiParam::new(I32)],
});
// We currently allocate all code segments independently, so nothing
// is colocated.
let colocated = false;
// FIXME: Use a real ExternalName system.
pos.func.import_function(ExtFuncData {
name: ExternalName::testcase("grow_memory"),
signature: sig_ref,
colocated,
})
});
self.grow_memory_extfunc = Some(grow_mem_func);
let call_inst = pos.ins().call(grow_mem_func, &[val]);
*pos.func.dfg.inst_results(call_inst).first().unwrap()
}
fn translate_current_memory(
&mut self,
mut pos: FuncCursor,
index: MemoryIndex,
_heap: ir::Heap,
) -> ir::Value {
debug_assert_eq!(index, 0, "non-default memories not supported yet");
let cur_mem_func = self.current_memory_extfunc.unwrap_or_else(|| {
let sig_ref = pos.func.import_signature(Signature {
call_conv: self.settings_flags.call_conv(),
argument_bytes: None,
params: Vec::new(),
returns: vec![AbiParam::new(I32)],
});
// We currently allocate all code segments independently, so nothing
// is colocated.
let colocated = false;
// FIXME: Use a real ExternalName system.
pos.func.import_function(ExtFuncData {
name: ExternalName::testcase("current_memory"),
signature: sig_ref,
colocated,
})
});
self.current_memory_extfunc = Some(cur_mem_func);
let call_inst = pos.ins().call(cur_mem_func, &[]);
*pos.func.dfg.inst_results(call_inst).first().unwrap()
}
}
/// This trait is useful for
/// `cretonne_wasm::translatemodule` because it
/// tells how to translate runtime-dependent wasm instructions. These functions should not be
/// called by the user.
impl<'data, 'module> cretonne_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data, 'module> {
fn get_func_name(&self, func_index: FunctionIndex) -> cretonne_codegen::ir::ExternalName {
get_func_name(func_index)
}
fn flags(&self) -> &settings::Flags {
self.settings_flags
}
fn declare_signature(&mut self, sig: &ir::Signature) {
let mut sig = sig.clone();
sig.params.push(AbiParam {
value_type: self.native_pointer(),
purpose: ArgumentPurpose::VMContext,
extension: ArgumentExtension::None,
location: ArgumentLoc::Unassigned,
});
// TODO: Deduplicate signatures.
self.module.signatures.push(sig);
}
fn get_signature(&self, sig_index: SignatureIndex) -> &ir::Signature {
&self.module.signatures[sig_index]
}
fn declare_func_import(&mut self, sig_index: SignatureIndex, module: &str, field: &str) {
debug_assert_eq!(
self.module.functions.len(),
self.module.imported_funcs.len(),
"Imported functions must be declared first"
);
self.module.functions.push(sig_index);
self.module.imported_funcs.push((
String::from(module),
String::from(field),
));
}
fn get_num_func_imports(&self) -> usize {
self.module.imported_funcs.len()
}
fn declare_func_type(&mut self, sig_index: SignatureIndex) {
self.module.functions.push(sig_index);
}
fn get_func_type(&self, func_index: FunctionIndex) -> SignatureIndex {
self.module.functions[func_index]
}
fn declare_global(&mut self, global: Global) {
self.module.globals.push(global);
}
fn get_global(&self, global_index: GlobalIndex) -> &cretonne_wasm::Global {
&self.module.globals[global_index]
}
fn declare_table(&mut self, table: Table) {
self.module.tables.push(table);
}
fn declare_table_elements(
&mut self,
table_index: TableIndex,
base: Option<GlobalIndex>,
offset: usize,
elements: Vec<FunctionIndex>,
) {
debug_assert!(base.is_none(), "global-value offsets not supported yet");
self.module.table_elements.push(module::TableElements {
table_index,
base,
offset,
elements,
});
}
fn declare_memory(&mut self, memory: Memory) {
self.module.memories.push(memory);
}
fn declare_data_initialization(
&mut self,
memory_index: MemoryIndex,
base: Option<GlobalIndex>,
offset: usize,
data: &'data [u8],
) {
debug_assert!(base.is_none(), "global-value offsets not supported yet");
self.lazy.data_initializers.push(DataInitializer {
memory_index,
base,
offset,
data,
});
}
fn declare_func_export(&mut self, func_index: FunctionIndex, name: &str) {
self.module.exports.insert(
String::from(name),
module::Export::Function(func_index),
);
}
fn declare_table_export(&mut self, table_index: TableIndex, name: &str) {
self.module.exports.insert(
String::from(name),
module::Export::Table(table_index),
);
}
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) {
self.module.exports.insert(
String::from(name),
module::Export::Memory(memory_index),
);
}
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) {
self.module.exports.insert(
String::from(name),
module::Export::Global(global_index),
);
}
fn declare_start_func(&mut self, func_index: FunctionIndex) {
debug_assert!(self.module.start_func.is_none());
self.module.start_func = Some(func_index);
}
fn define_function_body(&mut self, body_bytes: &'data [u8]) -> Result<(), String> {
self.lazy.function_body_inputs.push(body_bytes);
Ok(())
}
}
/// A record of a relocation to perform.
#[derive(Debug)]
pub struct Relocation {
/// The relocation code.
pub reloc: binemit::Reloc,
/// The function index.
pub func_index: FunctionIndex,
/// The offset where to apply the relocation.
pub offset: binemit::CodeOffset,
/// The addend to add to the relocation value.
pub addend: binemit::Addend,
}
/// Relocations to apply to function bodies.
pub type Relocations = Vec<Vec<Relocation>>;
/// The result of translating via `ModuleEnvironment`.
pub struct ModuleTranslation<'data, 'module> {
/// Compilation setting flags.
pub flags: &'module settings::Flags,
/// Module information.
pub module: &'module Module,
/// Pointers into the raw data buffer.
pub lazy: LazyContents<'data>,
}
/// Convenience functions for the user to be called after execution for debug purposes.
impl<'data, 'module> ModuleTranslation<'data, 'module> {
fn func_env(&self) -> FuncEnvironment {
FuncEnvironment::new(&self.flags, &self.module)
}
/// Compile the module, producing a compilation result with associated
/// relocations.
pub fn compile(
&self,
isa: &isa::TargetIsa,
) -> Result<(Compilation<'module>, Relocations), String> {
let mut functions = Vec::new();
let mut relocations = Vec::new();
for (i, input) in self.lazy.function_body_inputs.iter().enumerate() {
let func_index = i + self.module.imported_funcs.len();
let mut context = cretonne_codegen::Context::new();
context.func.name = get_func_name(func_index);
context.func.signature = self.module.signatures[self.module.functions[func_index]]
.clone();
let mut trans = FuncTranslator::new();
let reader = wasmparser::BinaryReader::new(input);
trans
.translate_from_reader(reader, &mut context.func, &mut self.func_env())
.map_err(|e| e.to_string())?;
let mut code_buf: Vec<u8> = Vec::new();
let mut reloc_sink = RelocSink::new();
let mut trap_sink = binemit::NullTrapSink {};
context
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
.map_err(|e| e.to_string())?;
functions.push(code_buf);
relocations.push(reloc_sink.func_relocs);
}
Ok((Compilation::new(self.module, functions), relocations))
}
}