More code reorganization.

This commit is contained in:
Dan Gohman
2018-08-03 13:45:46 -07:00
parent 6659ef7018
commit 831b481f13
10 changed files with 177 additions and 180 deletions

113
lib/execute/src/execute.rs Normal file
View File

@@ -0,0 +1,113 @@
use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::isa::TargetIsa;
use instance::Instance;
use region::protect;
use region::Protection;
use std::mem::transmute;
use std::ptr::write_unaligned;
use wasmtime_runtime::{compile_module, Compilation, Module, ModuleTranslation, Relocation};
/// Executes a module that has been translated with the `standalone::Runtime` runtime implementation.
pub fn compile_and_link_module<'data, 'module>(
isa: &TargetIsa,
translation: &ModuleTranslation<'data, 'module>,
) -> Result<Compilation, String> {
debug_assert!(
translation.module.start_func.is_none()
|| translation.module.start_func.unwrap() >= translation.module.imported_funcs.len(),
"imported start functions not supported yet"
);
let (mut compilation, relocations) = compile_module(&translation, isa)?;
// Apply relocations, now that we have virtual addresses for everything.
relocate(&mut compilation, &relocations);
Ok(compilation)
}
/// Performs the relocations inside the function bytecode, provided the necessary metadata
fn relocate(compilation: &mut Compilation, relocations: &[Vec<Relocation>]) {
// The relocations are relative to the relocation's address plus four bytes
// TODO: Support architectures other than x64, and other reloc kinds.
for (i, function_relocs) in relocations.iter().enumerate() {
for r in function_relocs {
let target_func_address: isize = compilation.functions[r.func_index].as_ptr() as isize;
let body = &mut compilation.functions[i];
match r.reloc {
Reloc::Abs8 => unsafe {
let reloc_address = body.as_mut_ptr().offset(r.offset as isize) as i64;
let reloc_addend = r.addend as i64;
let reloc_abs = target_func_address as i64 + reloc_addend;
write_unaligned(reloc_address as *mut i64, reloc_abs);
},
Reloc::X86PCRel4 => unsafe {
let reloc_address = body.as_mut_ptr().offset(r.offset as isize) as isize;
let reloc_addend = r.addend as isize;
// TODO: Handle overflow.
let reloc_delta_i32 =
(target_func_address - reloc_address + reloc_addend) as i32;
write_unaligned(reloc_address as *mut i32, reloc_delta_i32);
},
_ => panic!("unsupported reloc kind"),
}
}
}
}
/// Create the VmCtx data structure for the JIT'd code to use. This must
/// match the VmCtx layout in the runtime.
fn make_vmctx(instance: &mut Instance) -> Vec<*mut u8> {
let mut memories = Vec::new();
let mut vmctx = Vec::new();
vmctx.push(instance.globals.as_mut_ptr());
for mem in &mut instance.memories {
memories.push(mem.as_mut_ptr());
}
vmctx.push(memories.as_mut_ptr() as *mut u8);
vmctx
}
/// Jumps to the code region of memory and execute the start function of the module.
pub fn execute(
module: &Module,
compilation: &Compilation,
instance: &mut Instance,
) -> Result<(), String> {
let start_index = module
.start_func
.ok_or_else(|| String::from("No start function defined, aborting execution"))?;
// TODO: Put all the function bodies into a page-aligned memory region, and
// then make them ReadExecute rather than ReadWriteExecute.
for code_buf in &compilation.functions {
match unsafe {
protect(
code_buf.as_ptr(),
code_buf.len(),
Protection::ReadWriteExecute,
)
} {
Ok(()) => (),
Err(err) => {
return Err(format!(
"failed to give executable permission to code: {}",
err
))
}
}
}
let code_buf = &compilation.functions[start_index];
let vmctx = make_vmctx(instance);
// Rather than writing inline assembly to jump to the code region, we use the fact that
// the Rust ABI for calling a function with no arguments and no return matches the one of
// the generated code.Thanks to this, we can transmute the code region into a first-class
// Rust function and call it.
unsafe {
let start_func = transmute::<_, fn(*const *mut u8)>(code_buf.as_ptr());
start_func(vmctx.as_ptr());
}
Ok(())
}

View File

@@ -0,0 +1,98 @@
//! An `Instance` contains all the runtime state used by execution of a wasm
//! module.
use cranelift_codegen::ir;
use cranelift_wasm::GlobalIndex;
use wasmtime_runtime::{DataInitializer, Module, TableElements};
const PAGE_SIZE: usize = 65536;
/// An Instance of a WebAssemby module.
#[derive(Debug)]
pub struct Instance {
/// WebAssembly table data.
pub tables: Vec<Vec<usize>>,
/// WebAssembly linear memory data.
pub memories: Vec<Vec<u8>>,
/// WebAssembly global variable data.
pub globals: Vec<u8>,
}
impl Instance {
/// Create a new `Instance`.
pub fn new(module: &Module, data_initializers: &[DataInitializer]) -> Self {
let mut result = Self {
tables: Vec::new(),
memories: Vec::new(),
globals: Vec::new(),
};
result.instantiate_tables(module, &module.table_elements);
result.instantiate_memories(module, data_initializers);
result.instantiate_globals(module);
result
}
/// Allocate memory in `self` for just the tables of the current module.
fn instantiate_tables(&mut self, module: &Module, table_initializers: &[TableElements]) {
debug_assert!(self.tables.is_empty());
self.tables.reserve_exact(module.tables.len());
for table in &module.tables {
let len = table.size;
let mut v = Vec::with_capacity(len);
v.resize(len, 0);
self.tables.push(v);
}
for init in table_initializers {
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
let to_init =
&mut self.tables[init.table_index][init.offset..init.offset + init.elements.len()];
to_init.copy_from_slice(&init.elements);
}
}
/// Allocate memory in `instance` for just the memories of the current module.
fn instantiate_memories(&mut self, module: &Module, data_initializers: &[DataInitializer]) {
debug_assert!(self.memories.is_empty());
// Allocate the underlying memory and initialize it to all zeros.
self.memories.reserve_exact(module.memories.len());
for memory in &module.memories {
let len = memory.pages_count * PAGE_SIZE;
let mut v = Vec::with_capacity(len);
v.resize(len, 0);
self.memories.push(v);
}
for init in data_initializers {
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
let to_init =
&mut self.memories[init.memory_index][init.offset..init.offset + init.data.len()];
to_init.copy_from_slice(init.data);
}
}
/// Allocate memory in `instance` for just the globals of the current module,
/// without any initializers applied yet.
fn instantiate_globals(&mut self, module: &Module) {
debug_assert!(self.globals.is_empty());
// Allocate the underlying memory and initialize it to all zeros.
let globals_data_size = module.globals.len() * 8;
self.globals.resize(globals_data_size, 0);
}
/// Returns a slice of the contents of allocated linear memory.
pub fn inspect_memory(&self, memory_index: usize, address: usize, len: usize) -> &[u8] {
&self
.memories
.get(memory_index)
.unwrap_or_else(|| panic!("no memory for index {}", memory_index))
[address..address + len]
}
/// Shows the value of a global variable.
pub fn inspect_global(&self, global_index: GlobalIndex, ty: ir::Type) -> &[u8] {
let offset = global_index * 8;
let len = ty.bytes() as usize;
&self.globals[offset..offset + len]
}
}

View File

@@ -7,115 +7,8 @@ extern crate cranelift_wasm;
extern crate region;
extern crate wasmtime_runtime;
use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::isa::TargetIsa;
use region::protect;
use region::Protection;
use std::mem::transmute;
use std::ptr::write_unaligned;
use wasmtime_runtime::{Compilation, Relocation, compile_module};
mod execute;
mod instance;
/// Executes a module that has been translated with the `standalone::Runtime` runtime implementation.
pub fn compile_and_link_module<'data, 'module>(
isa: &TargetIsa,
translation: &wasmtime_runtime::ModuleTranslation<'data, 'module>,
) -> Result<wasmtime_runtime::Compilation<'module>, String> {
debug_assert!(
translation.module.start_func.is_none()
|| translation.module.start_func.unwrap() >= translation.module.imported_funcs.len(),
"imported start functions not supported yet"
);
let (mut compilation, relocations) = compile_module(&translation, isa)?;
// Apply relocations, now that we have virtual addresses for everything.
relocate(&mut compilation, &relocations);
Ok(compilation)
}
/// Performs the relocations inside the function bytecode, provided the necessary metadata
fn relocate(compilation: &mut Compilation, relocations: &[Vec<Relocation>]) {
// The relocations are relative to the relocation's address plus four bytes
// TODO: Support architectures other than x64, and other reloc kinds.
for (i, function_relocs) in relocations.iter().enumerate() {
for ref r in function_relocs {
let target_func_address: isize = compilation.functions[r.func_index].as_ptr() as isize;
let body = &mut compilation.functions[i];
match r.reloc {
Reloc::Abs8 => unsafe {
let reloc_address = body.as_mut_ptr().offset(r.offset as isize) as i64;
let reloc_addend = r.addend as i64;
let reloc_abs = target_func_address as i64 + reloc_addend;
write_unaligned(reloc_address as *mut i64, reloc_abs);
},
Reloc::X86PCRel4 => unsafe {
let reloc_address = body.as_mut_ptr().offset(r.offset as isize) as isize;
let reloc_addend = r.addend as isize;
// TODO: Handle overflow.
let reloc_delta_i32 =
(target_func_address - reloc_address + reloc_addend) as i32;
write_unaligned(reloc_address as *mut i32, reloc_delta_i32);
},
_ => panic!("unsupported reloc kind"),
}
}
}
}
/// Create the VmCtx data structure for the JIT'd code to use. This must
/// match the VmCtx layout in the runtime.
fn make_vmctx(instance: &mut wasmtime_runtime::Instance) -> Vec<*mut u8> {
let mut memories = Vec::new();
let mut vmctx = Vec::new();
vmctx.push(instance.globals.as_mut_ptr());
for mem in &mut instance.memories {
memories.push(mem.as_mut_ptr());
}
vmctx.push(memories.as_mut_ptr() as *mut u8);
vmctx
}
/// Jumps to the code region of memory and execute the start function of the module.
pub fn execute(
compilation: &wasmtime_runtime::Compilation,
instance: &mut wasmtime_runtime::Instance,
) -> Result<(), String> {
let start_index = compilation
.module
.start_func
.ok_or_else(|| String::from("No start function defined, aborting execution"))?;
// TODO: Put all the function bodies into a page-aligned memory region, and
// then make them ReadExecute rather than ReadWriteExecute.
for code_buf in &compilation.functions {
match unsafe {
protect(
code_buf.as_ptr(),
code_buf.len(),
Protection::ReadWriteExecute,
)
} {
Ok(()) => (),
Err(err) => {
return Err(format!(
"failed to give executable permission to code: {}",
err
))
}
}
}
let code_buf = &compilation.functions[start_index];
let vmctx = make_vmctx(instance);
// Rather than writing inline assembly to jump to the code region, we use the fact that
// the Rust ABI for calling a function with no arguments and no return matches the one of
// the generated code.Thanks to this, we can transmute the code region into a first-class
// Rust function and call it.
unsafe {
let start_func = transmute::<_, fn(*const *mut u8)>(code_buf.as_ptr());
start_func(vmctx.as_ptr());
}
Ok(())
}
pub use execute::{compile_and_link_module, execute};
pub use instance::Instance;