Fix table and memory import and improve memory storage

This commit is contained in:
Steve Akinyemi
2018-11-16 16:55:49 +01:00
parent 9227e16b98
commit df482667aa
21 changed files with 1798 additions and 808 deletions

View File

@ -15,15 +15,20 @@ use std::iter::Iterator;
use std::ptr::write_unaligned;
use std::slice;
use std::sync::Arc;
use std::mem::size_of;
use super::super::common::slice::{BoundedSlice, UncheckedSlice};
use super::errors::ErrorKind;
use super::import_object::ImportObject;
use super::import_object::{ImportObject, ImportValue};
use super::memory::LinearMemory;
use super::module::Export;
use super::module::Module;
use super::relocation::{Reloc, RelocSink, RelocationType};
type TablesSlice = UncheckedSlice<BoundedSlice<usize>>;
type MemoriesSlice = UncheckedSlice<BoundedSlice<u8>>;
type GlobalsSlice = UncheckedSlice<u8>;
pub fn protect_codebuf(code_buf: &Vec<u8>) -> Result<(), String> {
match unsafe {
region::protect(
@ -57,28 +62,15 @@ fn get_function_addr(
func_pointer
}
// TODO: To be removed.
// #[derive(Debug)]
// #[repr(C, packed)]
// pub struct VmCtx<'phantom> {
// pub user_data: UserData,
// globals: UncheckedSlice<u8>,
// memories: UncheckedSlice<UncheckedSlice<u8>>,
// tables: UncheckedSlice<BoundedSlice<usize>>,
// phantom: PhantomData<&'phantom ()>,
// }
// // TODO: To be removed.
// #[derive(Debug)]
// #[repr(C, packed)]
// pub struct UserData {
// // pub process: Dispatch<Process>,
// pub instance: Instance,
// }
/// An Instance of a WebAssembly module
/// NOTE: There is an assumption that data_pointers is always the
/// first field
#[repr(C)]
#[derive(Debug)]
pub struct Instance {
// C-like pointers to data (heaps, globals, tables)
pub data_pointers: DataPointers,
/// WebAssembly table data
// pub tables: Arc<Vec<RwLock<Vec<usize>>>>,
pub tables: Arc<Vec<Vec<usize>>>,
@ -100,41 +92,62 @@ pub struct Instance {
pub start_func: Option<FuncIndex>,
// Region start memory location
// code_base: *const (),
// C-like pointers to data (heaps, globals, tables)
pub data_pointers: DataPointers,
// Default memory bound
// TODO: Support for only one LinearMemory for now.
pub default_memory_bound: i32,
}
/// Contains pointers to data (heaps, globals, tables) needed
/// by Cranelift.
/// NOTE: Rearranging the fields will break the memory arrangement model
#[repr(C)]
#[derive(Debug)]
pub struct DataPointers {
// Pointer to tables
pub tables: UncheckedSlice<BoundedSlice<usize>>,
pub tables: TablesSlice,
// Pointer to memories
pub memories: UncheckedSlice<UncheckedSlice<u8>>,
pub memories: MemoriesSlice,
// Pointer to globals
pub globals: UncheckedSlice<u8>,
pub globals: GlobalsSlice,
}
impl Instance {
pub const TABLES_OFFSET: usize = 0; // 0 on 64-bit | 0 on 32-bit
pub const MEMORIES_OFFSET: usize = size_of::<TablesSlice>(); // 8 on 64-bit | 4 on 32-bit
pub const GLOBALS_OFFSET: usize = Instance::MEMORIES_OFFSET + size_of::<MemoriesSlice>(); // 16 on 64-bit | 8 on 32-bit
/// Create a new `Instance`.
/// TODO: Raise an error when expected import is not part of imported object
/// Also make sure imports that are not declared do not get added to the instance
pub fn new(
module: &Module,
import_object: &ImportObject<&str, &str>,
import_object: ImportObject<&str, &str>,
) -> Result<Instance, ErrorKind> {
let mut tables: Vec<Vec<usize>> = Vec::new();
let mut memories: Vec<LinearMemory> = Vec::new();
let mut globals: Vec<u8> = Vec::new();
let mut functions: Vec<Vec<u8>> = Vec::new();
let mut import_functions: Vec<*const u8> = Vec::new();
// let mut code_base: *const () = ptr::null();
let mut imported_functions: Vec<((&str, &str), *const u8)> = Vec::new();
let mut imported_tables: Vec<((&str, &str), Vec<usize>)> = Vec::new();
let mut imported_memories: Vec<((&str, &str), LinearMemory)> = Vec::new();
let mut imported_globals: Vec<((&str, &str), u8)> = Vec::new();
// Looping through and getting the imported objects
for (key, value) in import_object.map {
match value {
ImportValue::Memory(value) =>
imported_memories.push(((key.0, key.1), value)),
ImportValue::Table(value) =>
imported_tables.push(((key.0, key.1), value)),
ImportValue::Global(value) =>
imported_globals.push(((key.0, key.1), value)),
ImportValue::Func(value) =>
imported_functions.push(((key.0, key.1), value)),
}
}
debug!("Instance - Instantiating functions");
// Instantiate functions
@ -145,19 +158,14 @@ impl Instance {
.finish(module.info.flags.clone());
let mut relocations = Vec::new();
// We walk through the imported functions and set the relocations
// for each of this functions to be an empty vector (as is defined outside of wasm)
// Walk through the imported functions and set the relocations
// TODO: Needs to be optimize the find part.
// I also think it relies on the order of import declaration in module
for (module, field) in module.info.imported_funcs.iter() {
let function = import_object
.get(&module.as_str(), &field.as_str())
.ok_or_else(|| {
ErrorKind::LinkError(format!(
"Imported function {}.{} was not provided in the import_functions",
module, field
))
})?;
// println!("GET FUNC {:?}", function);
import_functions.push(function);
let function =
imported_functions.iter().find(|(key, _value)| key.0 == module && key.1 == field)
.unwrap_or_else(|| panic!("Imported function {}.{} was not provided in the import_functions", module, field));
import_functions.push(function.1);
relocations.push(vec![]);
}
@ -268,61 +276,35 @@ impl Instance {
}
}
}
// We only want to allocate in memory if there is more than
// 0 functions. Otherwise reserving a 0-sized memory region
// cause a panic error
// if total_size > 0 {
// // Allocate the total memory for this functions
// // let map = MmapMut::map_anon(total_size).unwrap();
// // let region_start = map.as_ptr() as usize;
// // code_base = map.as_ptr() as *const ();
// // // Emit this functions to memory
// for (ref func_context, func_offset) in context_and_offsets.iter() {
// let mut trap_sink = TrapSink::new(*func_offset);
// let mut reloc_sink = RelocSink::new();
// let mut code_buf: Vec<u8> = Vec::new();
// // let mut func_pointer = as *mut u8;
// unsafe {
// func_context.emit_to_memory(
// &*isa,
// &mut code_buf,
// &mut reloc_sink,
// &mut trap_sink,
// );
// };
// let func_offset = code_buf.as_ptr() as usize;
// functions.push(*func_offset);
// }
// // Set protection of this memory region to Read + Execute
// // so we are able to execute the functions emitted to memory
// // unsafe {
// // region::protect(region_start as *mut u8, total_size, region::Protection::ReadExecute)
// // .expect("unable to make memory readable+executable");
// // }
// }
}
debug!("Instance - Instantiating tables");
// Instantiate tables
{
// Reserve table space
tables.reserve_exact(module.info.tables.len());
// Reserve space for tables
tables.reserve_exact(imported_tables.len() + module.info.tables.len());
// Get imported tables
for (_key, table) in imported_tables {
tables.push(table);
}
// Get tables in module
for table in &module.info.tables {
let len = table.entity.size;
let mut v = Vec::with_capacity(len);
v.resize(len, 0);
tables.push(v);
}
// instantiate tables
for table_element in &module.info.table_elements {
// TODO: We shouldn't assert here since we are returning a Result<Instance, ErrorKind>
assert!(
table_element.base.is_none(),
"globalvalue base not supported yet."
);
let base = 0;
let table = &mut tables[table_element.table_index];
@ -342,22 +324,24 @@ impl Instance {
debug!("Instance - Instantiating memories");
// Instantiate memories
{
// Allocate the underlying memory and initialize it to all zeros.
let total_memories = module.info.memories.len();
if total_memories > 0 {
memories.reserve_exact(total_memories);
for memory in &module.info.memories {
let memory = memory.entity;
let v = LinearMemory::new(
memory.pages_count as u32,
memory.maximum.map(|m| m as u32),
);
memories.push(v);
}
} else {
memories.reserve_exact(1);
memories.push(LinearMemory::new(0, None));
// Reserve space for memories
memories.reserve_exact(imported_memories.len() + module.info.memories.len());
// Get imported memories
for (_key, memory) in imported_memories {
memories.push(memory);
}
// Get memories in module
for memory in &module.info.memories {
let memory = memory.entity;
let v = LinearMemory::new(
memory.pages_count as u32,
memory.maximum.map(|m| m as u32),
);
memories.push(v);
}
for init in &module.info.data_initializers {
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
let offset = init.offset;
@ -368,6 +352,7 @@ impl Instance {
}
debug!("Instance - Instantiating globals");
// TODO: Fix globals import
// Instantiate Globals
{
let globals_count = module.info.globals.len();
@ -379,6 +364,7 @@ impl Instance {
let globals_data = unsafe {
slice::from_raw_parts_mut(globals.as_mut_ptr() as *mut i64, globals_count)
};
for (i, global) in module.info.globals.iter().enumerate() {
let value: i64 = match global.entity.initializer {
GlobalInit::I32Const(n) => n as _,
@ -412,9 +398,11 @@ impl Instance {
// TODO: Refactor repetitive code
let tables_pointer: Vec<BoundedSlice<usize>> =
tables.iter().map(|table| table[..].into()).collect();
let memories_pointer: Vec<UncheckedSlice<u8>> =
memories.iter().map(|mem| mem[..].into()).collect();
let globals_pointer: UncheckedSlice<u8> = globals[..].into();
let memories_pointer: Vec<BoundedSlice<u8>> =
memories.iter().map(
|mem| BoundedSlice::new(&mem[..], mem.current as usize * LinearMemory::WASM_PAGE_SIZE),
).collect();
let globals_pointer: GlobalsSlice = globals[..].into();
let data_pointers = DataPointers {
memories: memories_pointer[..].into(),
@ -422,18 +410,16 @@ impl Instance {
tables: tables_pointer[..].into(),
};
let default_memory_bound = LinearMemory::WASM_PAGE_SIZE as i32;
// let mem = data_pointers.memories;
Ok(Instance {
data_pointers,
tables: Arc::new(tables.into_iter().collect()), // tables.into_iter().map(|table| RwLock::new(table)).collect()),
memories: Arc::new(memories.into_iter().collect()),
globals,
functions,
import_functions,
start_func,
data_pointers,
default_memory_bound,
// code_base: code_base,
})
}
@ -461,37 +447,6 @@ impl Instance {
}
}
// TODO: To be removed.
// pub fn generate_context(&self) -> VmCtx {
// let memories: Vec<UncheckedSlice<u8>> =
// self.memories.iter().map(|mem| mem[..].into()).collect();
// let tables: Vec<BoundedSlice<usize>> =
// self.tables.iter().map(|table| table[..].into()).collect();
// let globals: UncheckedSlice<u8> = self.globals[..].into();
// // println!("GENERATING CONTEXT {:?}", self.globals);
// // assert!(memories.len() >= 1, "modules must have at least one memory");
// // the first memory has a space of `mem::size_of::<VmCtxData>()` rounded
// // up to the 4KiB before it. We write the VmCtxData into that.
// let instance = self.clone();
// VmCtx {
// globals: globals,
// memories: memories[..].into(),
// tables: tables[..].into(),
// user_data: UserData {
// // process,
// instance: instance,
// },
// phantom: PhantomData,
// }
// // let main_heap_ptr = memories[0].as_mut_ptr() as *mut VmCtxData;
// // unsafe {
// // main_heap_ptr.sub(1).write(data);
// // &*(main_heap_ptr as *const VmCtx)
// // }
// }
/// Returns a slice of the contents of allocated linear memory.
pub fn inspect_memory(&self, memory_index: usize, address: usize, len: usize) -> &[u8] {
&self
@ -513,46 +468,6 @@ impl Instance {
// }
}
impl Clone for Instance {
fn clone(&self) -> Instance {
// TODO: Refactor repetitive code
let tables_pointer: Vec<BoundedSlice<usize>> =
self.tables.iter().map(|table| table[..].into()).collect();
let memories_pointer: Vec<UncheckedSlice<u8>> =
self.memories.iter().map(|mem| mem[..].into()).collect();
let globals_pointer: UncheckedSlice<u8> = self.globals[..].into();
let data_pointers = DataPointers {
memories: memories_pointer[..].into(),
globals: globals_pointer,
tables: tables_pointer[..].into(),
};
let default_memory_bound = self.memories.get(0).unwrap().current as i32;
Instance {
tables: Arc::clone(&self.tables),
memories: Arc::clone(&self.memories),
globals: self.globals.clone(),
functions: self.functions.clone(),
start_func: self.start_func.clone(),
import_functions: self.import_functions.clone(),
data_pointers,
default_memory_bound,
// code_base: self.code_base,
}
}
}
/// TODO:
/// Need to improve how memories are stored and grown.
/// Dynamic memory is inefficient both for growing and for access
/// Cranelift's dynamic heap assumes a _statically-known_ number of LinearMemories,
/// because it expects a corresponding global variable for each LinearMemory
///
/// Reference:
/// - https://cranelift.readthedocs.io/en/latest/ir.html?highlight=vmctx#heap-examples,
///
extern "C" fn grow_memory(size: u32, memory_index: u32, instance: &mut Instance) -> i32 {
// TODO: Support for only one LinearMemory for now.
debug_assert_eq!(
@ -565,31 +480,13 @@ extern "C" fn grow_memory(size: u32, memory_index: u32, instance: &mut Instance)
.grow(size)
.unwrap_or(i32::max_value()); // Should be -1 ?
// Update the default_memory_bound
instance.default_memory_bound =
(instance.memories.get(0).unwrap().current as usize * LinearMemory::WASM_PAGE_SIZE) as i32;
// Get new memory bytes
let new_mem_bytes = (old_mem_size as usize + size as usize) * LinearMemory::WASM_PAGE_SIZE;
// The grown memory changed so data_pointers need to be updated as well.
// TODO: Refactor repetitive code
let tables_pointer: Vec<BoundedSlice<usize>> = instance
.tables
.iter()
.map(|table| table[..].into())
.collect();
let memories_pointer: Vec<UncheckedSlice<u8>> =
instance.memories.iter().map(|mem| mem[..].into()).collect();
let globals_pointer: UncheckedSlice<u8> = instance.globals[..].into();
// Update data_pointer
instance.data_pointers.memories.get_unchecked_mut(memory_index as usize).len = new_mem_bytes;
let data_pointers = DataPointers {
memories: memories_pointer[..].into(),
globals: globals_pointer,
tables: tables_pointer[..].into(),
};
// Update data_pointers
instance.data_pointers = data_pointers;
return old_mem_size;
old_mem_size
}
extern "C" fn current_memory(memory_index: u32, instance: &mut Instance) -> u32 {