//! A webassembly::Instance object is a stateful, executable instance of a //! webassembly::Module. Instance objects contain all the Exported //! WebAssembly functions that allow calling into WebAssembly code. //! The webassembly::Instance() constructor function can be called to //! synchronously instantiate a given webassembly::Module object. However, the //! primary way to get an Instance is through the asynchronous //! webassembly::instantiate_streaming() function. use cranelift_codegen::ir::{Function, LibCall}; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::{binemit, Context}; use cranelift_entity::EntityRef; use cranelift_wasm::{FuncIndex, GlobalInit}; use rayon::prelude::*; use region; use std::iter::FromIterator; use std::iter::Iterator; use std::mem::size_of; use std::ptr::write_unaligned; use std::slice; use std::sync::Arc; use super::super::common::slice::{BoundedSlice, UncheckedSlice}; use super::super::recovery; use super::errors::ErrorKind; use super::import_object::{ImportObject, ImportValue}; use super::math_intrinsics; use super::memory::LinearMemory; use super::module::{Export, ImportableExportable, Module}; use super::relocation::{Reloc, RelocSink, RelocationType}; type TablesSlice = UncheckedSlice>; type MemoriesSlice = UncheckedSlice>; type GlobalsSlice = UncheckedSlice; pub fn protect_codebuf(code_buf: &Vec) -> Result<(), String> { match unsafe { region::protect( code_buf.as_ptr(), code_buf.len(), region::Protection::ReadWriteExecute, ) } { Err(err) => { return Err(format!( "failed to give executable permission to code: {}", err )) } Ok(()) => Ok(()), } } fn get_function_addr( func_index: &FuncIndex, import_functions: &Vec<*const u8>, functions: &Vec>, ) -> *const u8 { let index = func_index.index(); let len = import_functions.len(); let func_pointer = if index < len { import_functions[index] } else { (functions[index - len]).as_ptr() }; func_pointer } /// An Instance of a WebAssembly module /// NOTE: There is an assumption that data_pointers is always the /// first field #[repr(C)] #[derive(Debug)] #[repr(C)] pub struct Instance { // C-like pointers to data (heaps, globals, tables) pub data_pointers: DataPointers, /// WebAssembly table data // pub tables: Arc>>>, pub tables: Arc>>, /// WebAssembly linear memory data pub memories: Arc>, /// WebAssembly global variable data pub globals: Vec, /// Webassembly functions // functions: Vec, functions: Vec>, /// Imported functions import_functions: Vec<*const u8>, /// The module start function pub start_func: Option, // Region start memory location // code_base: *const (), } /// Contains pointers to data (heaps, globals, tables) needed /// by Cranelift. /// NOTE: Rearranging the fields will break the memory arrangement model #[repr(C)] #[derive(Debug)] #[repr(C)] pub struct DataPointers { // Pointer to tables pub tables: TablesSlice, // Pointer to memories pub memories: MemoriesSlice, // Pointer to globals pub globals: GlobalsSlice, } pub struct InstanceOptions { // Shall we mock automatically the imported functions if they don't exist? pub mock_missing_imports: bool, pub mock_missing_globals: bool, pub mock_missing_tables: bool, pub isa: Box, } extern "C" fn mock_fn() -> i32 { return 0; } struct CompiledFunction { code_buf: Vec, reloc_sink: RelocSink, trap_sink: binemit::NullTrapSink, } fn compile_function( isa: &TargetIsa, function_body: &Function, ) -> Result { let mut func_context = Context::for_function(function_body.to_owned()); let mut code_buf: Vec = Vec::new(); let mut reloc_sink = RelocSink::new(); let mut trap_sink = binemit::NullTrapSink {}; func_context .compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink) .map_err(|e| { debug!("CompileError: {}", e.to_string()); ErrorKind::CompileError(e.to_string()) })?; Ok(CompiledFunction { code_buf, reloc_sink, trap_sink, }) } impl Instance { pub const TABLES_OFFSET: usize = 0; // 0 on 64-bit | 0 on 32-bit pub const MEMORIES_OFFSET: usize = size_of::(); // 8 on 64-bit | 4 on 32-bit pub const GLOBALS_OFFSET: usize = Instance::MEMORIES_OFFSET + size_of::(); // 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>, options: InstanceOptions, ) -> Result { let mut tables: Vec> = Vec::new(); let mut memories: Vec = Vec::new(); let mut globals: Vec = Vec::new(); let mut functions: Vec> = Vec::new(); let mut import_functions: Vec<*const u8> = Vec::new(); debug!("Instance - Instantiating functions"); // Instantiate functions { functions.reserve_exact(module.info.functions.len()); let mut relocations = Vec::new(); // let imported_functions: Vec = module.info.imported_funcs.iter().map(|(module, field)| { // format!(" * {}.{}", module, field) // }).collect(); // println!("Instance imported functions: \n{}", imported_functions.join("\n")); // 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) for (module, field) in module.info.imported_funcs.iter() { let imported = import_object.get(&module.as_str(), &field.as_str()); let function: &*const u8 = match imported { Some(ImportValue::Func(f)) => f, None => { if options.mock_missing_imports { debug!( "The import {}.{} is not provided, therefore will be mocked.", module, field ); &(mock_fn as *const u8) } else { return Err(ErrorKind::LinkError(format!( "Imported function {}.{} was not provided in the import_functions", module, field ))); } } other => panic!("Expected function import, received {:?}", other), }; // println!("GET FUNC {:?}", function); import_functions.push(*function); relocations.push(vec![]); } debug!("Instance - Compiling functions"); // Compile the functions (from cranelift IR to machine code) let values: Vec<&Function> = Vec::from_iter(module.info.function_bodies.values()); // let isa: &TargetIsa = &*options.isa; let compiled_funcs: Vec = values .par_iter() .map(|function_body| -> CompiledFunction { // let r = *Arc::from_raw(isa_ptr); compile_function(&*options.isa, function_body).unwrap() // unimplemented!() }) .collect(); for compiled_func in compiled_funcs.into_iter() { let CompiledFunction { code_buf, reloc_sink, .. } = compiled_func; // let func_offset = code_buf; protect_codebuf(&code_buf).unwrap(); functions.push(code_buf); // context_and_offsets.push(func_context); relocations.push(reloc_sink.func_relocs); } // compiled_funcs?; debug!("Instance - Relocating functions"); // For each of the functions used, we see what are the calls inside this functions // and relocate each call to the proper memory address. // 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 reloc in function_relocs { let target_func_address: isize = match reloc.target { RelocationType::Normal(func_index) => { get_function_addr(&FuncIndex::new(func_index as usize), &import_functions, &functions) as isize }, RelocationType::CurrentMemory => { current_memory as isize }, RelocationType::GrowMemory => { grow_memory as isize }, RelocationType::LibCall(LibCall::CeilF32) => { math_intrinsics::ceilf32 as isize }, RelocationType::LibCall(LibCall::FloorF32) => { math_intrinsics::floorf32 as isize }, RelocationType::LibCall(LibCall::TruncF32) => { math_intrinsics::truncf32 as isize }, RelocationType::LibCall(LibCall::NearestF32) => { math_intrinsics::nearbyintf32 as isize }, RelocationType::LibCall(LibCall::CeilF64) => { math_intrinsics::ceilf64 as isize }, RelocationType::LibCall(LibCall::FloorF64) => { math_intrinsics::floorf64 as isize }, RelocationType::LibCall(LibCall::TruncF64) => { math_intrinsics::truncf64 as isize }, RelocationType::LibCall(LibCall::NearestF64) => { math_intrinsics::nearbyintf64 as isize }, _ => unimplemented!() // RelocationType::Intrinsic(name) => { // get_abi_intrinsic(name)? // }, }; let func_addr = get_function_addr(&FuncIndex::new(i), &import_functions, &functions); match reloc.reloc { Reloc::Abs8 => unsafe { let reloc_address = func_addr.offset(reloc.offset as isize) as i64; let reloc_addend = reloc.addend; 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 = func_addr.offset(reloc.offset as isize) as isize; let reloc_addend = reloc.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"), } } } } debug!("Instance - Instantiating globals"); // Instantiate Globals let globals_data = { let globals_count = module.info.globals.len(); // Allocate the underlying memory and initialize it to zeros let globals_data_size = globals_count * 8; globals.resize(globals_data_size, 0); // cast the globals slice to a slice of i64. 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 ImportableExportable { entity, import_name, .. } = global; let value: i64 = match entity.initializer { GlobalInit::I32Const(n) => n as _, GlobalInit::I64Const(n) => n, GlobalInit::F32Const(f) => f as _, // unsafe { mem::transmute(f as f64) }, GlobalInit::F64Const(f) => f as _, // unsafe { mem::transmute(f) }, GlobalInit::GlobalRef(global_index) => globals_data[global_index.index()], GlobalInit::Import() => { let (module_name, field_name) = import_name .as_ref() .expect("Expected a import name for the global import"); let imported = import_object.get(&module_name.as_str(), &field_name.as_str()); match imported { Some(ImportValue::Global(value)) => *value, None => { if options.mock_missing_globals { 0 } else { panic!( "Imported global value was not provided ({}.{})", module_name, field_name ) } } _ => panic!( "Expected global import, but received {:?} ({}.{})", imported, module_name, field_name ), } } }; globals_data[i] = value; } globals_data }; debug!("Instance - Instantiating tables"); // Instantiate tables { // Reserve space for tables tables.reserve_exact(module.info.tables.len()); // Get tables in module for table in &module.info.tables { let table: Vec = match table.import_name.as_ref() { Some((module_name, field_name)) => { let imported = import_object.get(&module_name.as_str(), &field_name.as_str()); match imported { Some(ImportValue::Table(t)) => t.to_vec(), None => { if options.mock_missing_tables { let len = table.entity.size; let mut v = Vec::with_capacity(len); v.resize(len, 0); v } else { panic!( "Imported table value was not provided ({}.{})", module_name, field_name ) } } _ => panic!( "Expected global table, but received {:?} ({}.{})", imported, module_name, field_name ), } } None => { let len = table.entity.size; let mut v = Vec::with_capacity(len); v.resize(len, 0); v } }; tables.push(table); } // instantiate tables for table_element in &module.info.table_elements { let base = match table_element.base { Some(global_index) => globals_data[global_index.index()] as usize, None => 0, }; let table = &mut tables[table_element.table_index.index()]; for (i, func_index) in table_element.elements.iter().enumerate() { // since the table just contains functions in the MVP // we get the address of the specified function indexes // to populate the table. // let func_index = *elem_index - module.info.imported_funcs.len() as u32; // let func_addr = functions[func_index.index()].as_ptr(); let func_addr = get_function_addr(&func_index, &import_functions, &functions); table[base + table_element.offset + i] = func_addr as _; } } } debug!("Instance - Instantiating memories"); // Instantiate memories { // Reserve space for memories memories.reserve_exact(module.info.memories.len()); // 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; let mem_mut = memories[init.memory_index.index()].as_mut(); let to_init = &mut mem_mut[offset..offset + init.data.len()]; to_init.copy_from_slice(&init.data); } } let start_func: Option = module .info .start_func .or_else(|| match module.info.exports.get("main") { Some(Export::Function(index)) => Some(*index), _ => None, }); // TODO: Refactor repetitive code let tables_pointer: Vec> = tables.iter().map(|table| table[..].into()).collect(); let memories_pointer: Vec> = 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(), globals: globals_pointer, tables: tables_pointer[..].into(), }; // 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, }) } pub fn memory_mut(&mut self, memory_index: usize) -> &mut LinearMemory { let memories = Arc::get_mut(&mut self.memories).unwrap_or_else(|| { panic!("Can't get memories as a mutable pointer (there might exist more mutable pointers to the memories)") }); memories .get_mut(memory_index) .unwrap_or_else(|| panic!("no memory for index {}", memory_index)) } pub fn memories(&self) -> Arc> { self.memories.clone() } pub fn get_function_pointer(&self, func_index: FuncIndex) -> *const u8 { get_function_addr(&func_index, &self.import_functions, &self.functions) } pub fn start(&self) -> Result<(), ErrorKind> { if let Some(func_index) = self.start_func { let func: fn(&Instance) = get_instance_function!(&self, func_index); call_protected!(func(self)) } else { Ok(()) } } /// 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)) .as_ref()[address..address + len] } pub fn memory_offset_addr(&self, index: usize, offset: usize) -> *const usize { let mem = &self.memories[index]; unsafe { mem.mmap.as_ptr().offset(offset as isize) as *const usize } } // 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] // } // pub fn start_func(&self) -> extern fn(&VmCtx) { // self.start_func // } } // TODO: Needs to be moved to more appropriate place 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!( memory_index, 0, "non-default memory_index (0) not supported yet" ); let old_mem_size = instance .memory_mut(memory_index as usize) .grow(size) .unwrap_or(-1); if old_mem_size != -1 { // Get new memory bytes let new_mem_bytes = (old_mem_size as usize + size as usize) * LinearMemory::WASM_PAGE_SIZE; // Update data_pointer instance .data_pointers .memories .get_unchecked_mut(memory_index as usize) .len = new_mem_bytes; } old_mem_size } extern "C" fn current_memory(memory_index: u32, instance: &mut Instance) -> u32 { let memory = &instance.memories[memory_index as usize]; memory.current_size() as u32 }