use crate::{ error::{LinkError, LinkResult}, export::{Context, Export}, global::Global, import::ImportObject, memory::Memory, module::{ImportName, ModuleInfo, ModuleInner}, sig_registry::SigRegistry, structures::{BoxedMap, Map, SliceMap, TypedIndex}, table::Table, types::{ ImportedFuncIndex, ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer, LocalFuncIndex, LocalGlobalIndex, LocalMemoryIndex, LocalOrImport, LocalTableIndex, SigIndex, Value, }, vm, }; use std::{fmt::Debug, slice}; pub const INTERNALS_SIZE: usize = 256; pub(crate) struct Internals(pub(crate) [u64; INTERNALS_SIZE]); impl Debug for Internals { fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(formatter, "Internals({:?})", &self.0[..]) } } /// The `LocalBacking` "owns" the memory used by all the local resources of an Instance. /// That is, local memories, tables, and globals (as well as some additional /// data for the virtual call machinery). #[derive(Debug)] pub struct LocalBacking { /// This is a map from the local resource index to actual memory, /// table, and globals. pub(crate) memories: BoxedMap, pub(crate) tables: BoxedMap, pub(crate) globals: BoxedMap, /// This own the memory containing the pointers to the local memories. /// While simplifying implementation, this adds indirection and may hurt /// performance, especially on cache-starved systems. pub(crate) vm_memories: BoxedMap, pub(crate) vm_tables: BoxedMap, pub(crate) vm_globals: BoxedMap, /// The dynamic sigindices are used to efficiently support caching and /// the `call_indirect` wasm instruction. This field (and local_functions /// as well) are subject to change. pub(crate) dynamic_sigindices: BoxedMap, pub(crate) local_functions: BoxedMap, pub(crate) internals: Internals, } impl LocalBacking { pub(crate) fn new( module: &ModuleInner, imports: &ImportBacking, vmctx: *mut vm::Ctx, ) -> LinkResult { let mut memories = Self::generate_memories(module); let mut tables = Self::generate_tables(module); let mut globals = Self::generate_globals(module, imports); let vm_memories = Self::finalize_memories(module, imports, &mut memories)?; let vm_tables = Self::finalize_tables(module, imports, &mut tables, vmctx)?; let vm_globals = Self::finalize_globals(&mut globals); let dynamic_sigindices = Self::generate_sigindices(&module.info); let local_functions = Self::generate_local_functions(module); Ok(Self { memories, tables, globals, vm_memories, vm_tables, vm_globals, dynamic_sigindices, local_functions, internals: Internals([0; INTERNALS_SIZE]), }) } fn generate_local_functions(module: &ModuleInner) -> BoxedMap { (0..module.info.func_assoc.len() - module.info.imported_functions.len()) .map(|index| { module .runnable_module .get_func(&module.info, LocalFuncIndex::new(index)) .unwrap() .as_ptr() as *const _ }) .collect::>() .into_boxed_map() } fn generate_sigindices(info: &ModuleInfo) -> BoxedMap { info.signatures .iter() .map(|(_, signature)| { let signature = SigRegistry.lookup_signature_ref(signature); let sig_index = SigRegistry.lookup_sig_index(signature); vm::SigId(sig_index.index() as u32) }) .collect::>() .into_boxed_map() } fn generate_memories(module: &ModuleInner) -> BoxedMap { let mut memories = Map::with_capacity(module.info.memories.len()); for (_, &desc) in &module.info.memories { memories.push(Memory::new(desc).expect("unable to create memory")); } memories.into_boxed_map() } /// Initialize each locally-defined memory in the Module. /// /// This involves copying in the data initializers. fn finalize_memories( module: &ModuleInner, imports: &ImportBacking, memories: &mut SliceMap, ) -> LinkResult> { // For each init that has some data... // Validate data size fits for init in module.info.data_initializers.iter() { let init_base = match init.base { Initializer::Const(Value::I32(offset)) => offset as u32, Initializer::Const(_) => panic!("a const initializer must be the i32 type"), Initializer::GetGlobal(import_global_index) => { if let Value::I32(x) = imports.globals[import_global_index].get() { x as u32 } else { panic!("unsupported global type for initializer") } } } as usize; // Validate data size fits match init.memory_index.local_or_import(&module.info) { LocalOrImport::Local(local_memory_index) => { let memory_desc = module.info.memories[local_memory_index]; let data_top = init_base + init.data.len(); if memory_desc.minimum.bytes().0 < data_top || data_top < init_base { return Err(vec![LinkError::Generic { message: "data segment does not fit".to_string(), }]); } } LocalOrImport::Import(imported_memory_index) => { // Write the initialization data to the memory that // we think the imported memory is. unsafe { let local_memory = &*imports.vm_memories[imported_memory_index]; let data_top = init_base + init.data.len(); if local_memory.bound < data_top || data_top < init_base { return Err(vec![LinkError::Generic { message: "data segment does not fit".to_string(), }]); } } } } } // Initialize data for init in module.info.data_initializers.iter() { let init_base = match init.base { Initializer::Const(Value::I32(offset)) => offset as u32, Initializer::Const(_) => panic!("a const initializer must be the i32 type"), Initializer::GetGlobal(import_global_index) => { if let Value::I32(x) = imports.globals[import_global_index].get() { x as u32 } else { panic!("unsupported global type for initializer") } } } as usize; match init.memory_index.local_or_import(&module.info) { LocalOrImport::Local(local_memory_index) => { let mem = &memories[local_memory_index]; for (mem_byte, data_byte) in mem.view()[init_base..init_base + init.data.len()] .iter() .zip(init.data.iter()) { mem_byte.set(*data_byte); } } LocalOrImport::Import(imported_memory_index) => { // Write the initialization data to the memory that // we think the imported memory is. unsafe { let local_memory = &*imports.vm_memories[imported_memory_index]; let memory_slice = slice::from_raw_parts_mut(local_memory.base, local_memory.bound); let mem_init_view = &mut memory_slice[init_base..init_base + init.data.len()]; mem_init_view.copy_from_slice(&init.data); } } } } Ok(memories .iter_mut() .map(|(_, mem)| mem.vm_local_memory()) .collect::>() .into_boxed_map()) } fn generate_tables(module: &ModuleInner) -> BoxedMap { let mut tables = Map::with_capacity(module.info.tables.len()); for (_, &table_desc) in module.info.tables.iter() { let table = Table::new(table_desc).unwrap(); tables.push(table); } tables.into_boxed_map() } /// This initializes all of the locally-defined tables in the Module, e.g. /// putting all the table elements (function pointers) /// in the right places. #[allow(clippy::cast_ptr_alignment)] fn finalize_tables( module: &ModuleInner, imports: &ImportBacking, tables: &mut SliceMap, vmctx: *mut vm::Ctx, ) -> LinkResult> { for init in &module.info.elem_initializers { let init_base = match init.base { Initializer::Const(Value::I32(offset)) => offset as u32, Initializer::Const(_) => panic!("a const initializer must be the i32 type"), Initializer::GetGlobal(import_global_index) => { if let Value::I32(x) = imports.globals[import_global_index].get() { x as u32 } else { panic!("unsupported global type for initializer") } } } as usize; match init.table_index.local_or_import(&module.info) { LocalOrImport::Local(local_table_index) => { let table = &tables[local_table_index]; if (table.size() as usize) < init_base + init.elements.len() { return Err(vec![LinkError::Generic { message: "elements segment does not fit".to_string(), }]); } table.anyfunc_direct_access_mut(|elements| { for (i, &func_index) in init.elements.iter().enumerate() { let sig_index = module.info.func_assoc[func_index]; // let signature = &module.info.signatures[sig_index]; let signature = SigRegistry .lookup_signature_ref(&module.info.signatures[sig_index]); let sig_id = vm::SigId(SigRegistry.lookup_sig_index(signature).index() as u32); let (func, ctx) = match func_index.local_or_import(&module.info) { LocalOrImport::Local(local_func_index) => ( module .runnable_module .get_func(&module.info, local_func_index) .unwrap() .as_ptr() as *const vm::Func, vmctx, ), LocalOrImport::Import(imported_func_index) => { let vm::ImportedFunc { func, vmctx } = imports.vm_functions[imported_func_index]; (func, vmctx) } }; elements[init_base + i] = vm::Anyfunc { func, ctx, sig_id }; } }); } LocalOrImport::Import(import_table_index) => { let table = &imports.tables[import_table_index]; if (table.size() as usize) < init_base + init.elements.len() { return Err(vec![LinkError::Generic { message: "elements segment does not fit".to_string(), }]); } table.anyfunc_direct_access_mut(|elements| { for (i, &func_index) in init.elements.iter().enumerate() { let sig_index = module.info.func_assoc[func_index]; let signature = SigRegistry .lookup_signature_ref(&module.info.signatures[sig_index]); // let signature = &module.info.signatures[sig_index]; let sig_id = vm::SigId(SigRegistry.lookup_sig_index(signature).index() as u32); let (func, ctx) = match func_index.local_or_import(&module.info) { LocalOrImport::Local(local_func_index) => ( module .runnable_module .get_func(&module.info, local_func_index) .unwrap() .as_ptr() as *const vm::Func, vmctx, ), LocalOrImport::Import(imported_func_index) => { let vm::ImportedFunc { func, vmctx } = imports.vm_functions[imported_func_index]; (func, vmctx) } }; elements[init_base + i] = vm::Anyfunc { func, ctx, sig_id }; } }); } } } Ok(tables .iter_mut() .map(|(_, table)| table.vm_local_table()) .collect::>() .into_boxed_map()) } fn generate_globals( module: &ModuleInner, imports: &ImportBacking, ) -> BoxedMap { let mut globals = Map::with_capacity(module.info.globals.len()); for (_, global_init) in module.info.globals.iter() { let value = match &global_init.init { Initializer::Const(value) => value.clone(), Initializer::GetGlobal(import_global_index) => { imports.globals[*import_global_index].get() } }; let global = if global_init.desc.mutable { Global::new_mutable(value) } else { Global::new(value) }; globals.push(global); } globals.into_boxed_map() } fn finalize_globals( globals: &mut SliceMap, ) -> BoxedMap { globals .iter_mut() .map(|(_, global)| global.vm_local_global()) .collect::>() .into_boxed_map() } } #[derive(Debug)] pub struct ImportBacking { pub(crate) memories: BoxedMap, pub(crate) tables: BoxedMap, pub(crate) globals: BoxedMap, pub(crate) vm_functions: BoxedMap, pub(crate) vm_memories: BoxedMap, pub(crate) vm_tables: BoxedMap, pub(crate) vm_globals: BoxedMap, } impl ImportBacking { pub fn new( module: &ModuleInner, imports: &ImportObject, vmctx: *mut vm::Ctx, ) -> LinkResult { let mut failed = false; let mut link_errors = vec![]; let vm_functions = import_functions(module, imports, vmctx).unwrap_or_else(|le| { failed = true; link_errors.extend(le); Map::new().into_boxed_map() }); let (memories, vm_memories) = import_memories(module, imports).unwrap_or_else(|le| { failed = true; link_errors.extend(le); (Map::new().into_boxed_map(), Map::new().into_boxed_map()) }); let (tables, vm_tables) = import_tables(module, imports).unwrap_or_else(|le| { failed = true; link_errors.extend(le); (Map::new().into_boxed_map(), Map::new().into_boxed_map()) }); let (globals, vm_globals) = import_globals(module, imports).unwrap_or_else(|le| { failed = true; link_errors.extend(le); (Map::new().into_boxed_map(), Map::new().into_boxed_map()) }); if failed { Err(link_errors) } else { Ok(ImportBacking { memories, tables, globals, vm_functions, vm_memories, vm_tables, vm_globals, }) } } pub fn imported_func(&self, index: ImportedFuncIndex) -> vm::ImportedFunc { self.vm_functions[index].clone() } } fn import_functions( module: &ModuleInner, imports: &ImportObject, vmctx: *mut vm::Ctx, ) -> LinkResult> { let mut link_errors = vec![]; let mut functions = Map::with_capacity(module.info.imported_functions.len()); for ( index, ImportName { namespace_index, name_index, }, ) in &module.info.imported_functions { let sig_index = module.info.func_assoc[index.convert_up(&module.info)]; let expected_sig = &module.info.signatures[sig_index]; let namespace = module.info.namespace_table.get(*namespace_index); let name = module.info.name_table.get(*name_index); let import = imports .get_namespace(namespace) .and_then(|namespace| namespace.get_export(name)); match import { Some(Export::Function { func, ctx, signature, }) => { if *expected_sig == *signature { functions.push(vm::ImportedFunc { func: func.inner(), vmctx: match ctx { Context::External(ctx) => ctx, Context::Internal => vmctx, }, }); } else { link_errors.push(LinkError::IncorrectImportSignature { namespace: namespace.to_string(), name: name.to_string(), expected: (*expected_sig).clone(), found: (*signature).clone(), }); } } Some(export_type) => { let export_type_name = match export_type { Export::Function { .. } => "function", Export::Memory { .. } => "memory", Export::Table { .. } => "table", Export::Global { .. } => "global", } .to_string(); link_errors.push(LinkError::IncorrectImportType { namespace: namespace.to_string(), name: name.to_string(), expected: "function".to_string(), found: export_type_name, }); } None => { if imports.allow_missing_functions { functions.push(vm::ImportedFunc { func: ::std::ptr::null(), vmctx: ::std::ptr::null_mut(), }); } else { link_errors.push(LinkError::ImportNotFound { namespace: namespace.to_string(), name: name.to_string(), }); } } } } if link_errors.len() > 0 { Err(link_errors) } else { Ok(functions.into_boxed_map()) } } fn import_memories( module: &ModuleInner, imports: &ImportObject, ) -> LinkResult<( BoxedMap, BoxedMap, )> { let mut link_errors = vec![]; let mut memories = Map::with_capacity(module.info.imported_memories.len()); let mut vm_memories = Map::with_capacity(module.info.imported_memories.len()); for ( _index, ( ImportName { namespace_index, name_index, }, expected_memory_desc, ), ) in &module.info.imported_memories { let namespace = module.info.namespace_table.get(*namespace_index); let name = module.info.name_table.get(*name_index); let memory_import = imports .get_namespace(&namespace) .and_then(|namespace| namespace.get_export(&name)); match memory_import { Some(Export::Memory(memory)) => { if expected_memory_desc.fits_in_imported(memory.descriptor()) { memories.push(memory.clone()); vm_memories.push(memory.vm_local_memory()); } else { link_errors.push(LinkError::IncorrectMemoryDescriptor { namespace: namespace.to_string(), name: name.to_string(), expected: *expected_memory_desc, found: memory.descriptor(), }); } } Some(export_type) => { let export_type_name = match export_type { Export::Function { .. } => "function", Export::Memory { .. } => "memory", Export::Table { .. } => "table", Export::Global { .. } => "global", } .to_string(); link_errors.push(LinkError::IncorrectImportType { namespace: namespace.to_string(), name: name.to_string(), expected: "memory".to_string(), found: export_type_name, }); } None => { link_errors.push(LinkError::ImportNotFound { namespace: namespace.to_string(), name: name.to_string(), }); } } } if link_errors.len() > 0 { Err(link_errors) } else { Ok((memories.into_boxed_map(), vm_memories.into_boxed_map())) } } fn import_tables( module: &ModuleInner, imports: &ImportObject, ) -> LinkResult<( BoxedMap, BoxedMap, )> { let mut link_errors = vec![]; let mut tables = Map::with_capacity(module.info.imported_tables.len()); let mut vm_tables = Map::with_capacity(module.info.imported_tables.len()); for ( _index, ( ImportName { namespace_index, name_index, }, expected_table_desc, ), ) in &module.info.imported_tables { let namespace = module.info.namespace_table.get(*namespace_index); let name = module.info.name_table.get(*name_index); let table_import = imports .get_namespace(&namespace) .and_then(|namespace| namespace.get_export(&name)); match table_import { Some(Export::Table(mut table)) => { if expected_table_desc.fits_in_imported(table.descriptor()) { vm_tables.push(table.vm_local_table()); tables.push(table); } else { link_errors.push(LinkError::IncorrectTableDescriptor { namespace: namespace.to_string(), name: name.to_string(), expected: *expected_table_desc, found: table.descriptor(), }); } } Some(export_type) => { let export_type_name = match export_type { Export::Function { .. } => "function", Export::Memory { .. } => "memory", Export::Table { .. } => "table", Export::Global { .. } => "global", } .to_string(); link_errors.push(LinkError::IncorrectImportType { namespace: namespace.to_string(), name: name.to_string(), expected: "table".to_string(), found: export_type_name, }); } None => { link_errors.push(LinkError::ImportNotFound { namespace: namespace.to_string(), name: name.to_string(), }); } } } if link_errors.len() > 0 { Err(link_errors) } else { Ok((tables.into_boxed_map(), vm_tables.into_boxed_map())) } } fn import_globals( module: &ModuleInner, imports: &ImportObject, ) -> LinkResult<( BoxedMap, BoxedMap, )> { let mut link_errors = vec![]; let mut globals = Map::with_capacity(module.info.imported_globals.len()); let mut vm_globals = Map::with_capacity(module.info.imported_globals.len()); for ( _, ( ImportName { namespace_index, name_index, }, imported_global_desc, ), ) in &module.info.imported_globals { let namespace = module.info.namespace_table.get(*namespace_index); let name = module.info.name_table.get(*name_index); let import = imports .get_namespace(namespace) .and_then(|namespace| namespace.get_export(name)); match import { Some(Export::Global(mut global)) => { if global.descriptor() == *imported_global_desc { vm_globals.push(global.vm_local_global()); globals.push(global); } else { link_errors.push(LinkError::IncorrectGlobalDescriptor { namespace: namespace.to_string(), name: name.to_string(), expected: *imported_global_desc, found: global.descriptor(), }); } } Some(export_type) => { let export_type_name = match export_type { Export::Function { .. } => "function", Export::Memory { .. } => "memory", Export::Table { .. } => "table", Export::Global { .. } => "global", } .to_string(); link_errors.push(LinkError::IncorrectImportType { namespace: namespace.to_string(), name: name.to_string(), expected: "global".to_string(), found: export_type_name, }); } None => { link_errors.push(LinkError::ImportNotFound { namespace: namespace.to_string(), name: name.to_string(), }); } } } if link_errors.len() > 0 { Err(link_errors) } else { Ok((globals.into_boxed_map(), vm_globals.into_boxed_map())) } }