diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index 2742b8a66..5f0189e12 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -41,7 +41,7 @@ maintenance = { status = "actively-developed" } default = ["wat", "cranelift", "jit"] compiler = ["wasmer-engine-jit/compiler"] engine = [] -jit = ["wasmer-engine-jit"] +jit = ["wasmer-engine-jit", "engine"] singlepass = [ "wasmer-compiler-singlepass", "compiler", diff --git a/lib/api/src/externals.rs b/lib/api/src/externals.rs index c7ff7ca18..48cd5b910 100644 --- a/lib/api/src/externals.rs +++ b/lib/api/src/externals.rs @@ -7,11 +7,14 @@ use crate::RuntimeError; use crate::{ExternType, FunctionType, GlobalType, MemoryType, TableType, ValType}; use std::cmp::max; use std::slice; -use wasm_common::{HostFunction, Pages, ValueType, WasmTypeList, WithEnv, WithoutEnv}; +use wasm_common::{ + HostFunction, Pages, SignatureIndex, ValueType, WasmTypeList, WithEnv, WithoutEnv, +}; use wasmer_runtime::{ wasmer_call_trampoline, Export, ExportFunction, ExportGlobal, ExportMemory, ExportTable, - LinearMemory, MemoryError, Table as RuntimeTable, VMCallerCheckedAnyfunc, VMContext, - VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTrampoline, + InstanceHandle, LinearMemory, MemoryError, Table as RuntimeTable, VMCallerCheckedAnyfunc, + VMContext, VMDynamicFunctionImportContext, VMFunctionBody, VMFunctionKind, VMGlobalDefinition, + VMMemoryDefinition, VMTrampoline, }; #[derive(Clone)] @@ -477,32 +480,26 @@ impl Drop for Memory { /// A function defined in the Wasm module #[derive(Clone, PartialEq)] -pub struct WasmFunc { +pub struct WasmFunctionDefinition { // The trampoline to do the call trampoline: VMTrampoline, } -/// A function defined in the Host -#[derive(Clone, PartialEq)] -pub struct HostFunc { - // func: wasm_common::Func, -} - /// The inner helper #[derive(Clone, PartialEq)] -pub enum InnerFunc { +pub enum FunctionDefinition { /// A function defined in the Wasm side - Wasm(WasmFunc), + Wasm(WasmFunctionDefinition), /// A function defined in the Host side - Host(HostFunc), + Host, } /// A WebAssembly `function`. #[derive(Clone, PartialEq)] pub struct Function { store: Store, + definition: FunctionDefinition, // If the Function is owned by the Store, not the instance - inner: InnerFunc, owned_by_store: bool, exported: ExportFunction, } @@ -519,21 +516,71 @@ impl Function { Rets: WasmTypeList, Env: Sized, { - let func: wasm_common::Func = wasm_common::Func::new(func); + let func: wasm_common::Func = wasm_common::Func::new(func); let address = func.address() as *const VMFunctionBody; - let vmctx = (func.env().unwrap_or(std::ptr::null_mut()) as *mut _) as *mut VMContext; + let vmctx = std::ptr::null_mut() as *mut _ as *mut VMContext; let func_type = func.ty(); let signature = store.engine().register_signature(&func_type); Self { store: store.clone(), owned_by_store: true, - inner: InnerFunc::Host(HostFunc { - // func - }), + definition: FunctionDefinition::Host, exported: ExportFunction { address, vmctx, signature, + kind: VMFunctionKind::Static, + }, + } + } + + #[allow(clippy::cast_ptr_alignment)] + pub fn new_dynamic(store: &Store, ty: &FunctionType, func: F) -> Self + where + F: Fn(&[Val]) -> Result, RuntimeError> + 'static, + { + let dynamic_ctx = + VMDynamicFunctionImportContext::from_context(VMDynamicFunctionWithoutEnv { + func: Box::new(func), + }); + let address = std::ptr::null() as *const () as *const VMFunctionBody; + let vmctx = Box::leak(Box::new(dynamic_ctx)) as *mut _ as *mut VMContext; + let signature = store.engine().register_signature(&ty); + Self { + store: store.clone(), + owned_by_store: true, + definition: FunctionDefinition::Host, + exported: ExportFunction { + address, + kind: VMFunctionKind::Dynamic, + vmctx, + signature, + }, + } + } + + #[allow(clippy::cast_ptr_alignment)] + pub fn new_dynamic_env(store: &Store, ty: &FunctionType, env: &mut Env, func: F) -> Self + where + F: Fn(&mut Env, &[Val]) -> Result, RuntimeError> + 'static, + Env: Sized, + { + let dynamic_ctx = VMDynamicFunctionImportContext::from_context(VMDynamicFunctionWithEnv { + env, + func: Box::new(func), + }); + let address = std::ptr::null() as *const () as *const VMFunctionBody; + let vmctx = Box::leak(Box::new(dynamic_ctx)) as *mut _ as *mut VMContext; + let signature = store.engine().register_signature(&ty); + Self { + store: store.clone(), + owned_by_store: true, + definition: FunctionDefinition::Host, + exported: ExportFunction { + address, + kind: VMFunctionKind::Dynamic, + vmctx, + signature, }, } } @@ -550,19 +597,23 @@ impl Function { Rets: WasmTypeList, Env: Sized, { - let func: wasm_common::Func = wasm_common::Func::new_env(env, func); + let func: wasm_common::Func = wasm_common::Func::new(func); let address = func.address() as *const VMFunctionBody; - let vmctx = (func.env().unwrap_or(std::ptr::null_mut()) as *mut _) as *mut VMContext; + // TODO: We need to refactor the Function context. + // Right now is structured as it's always a `VMContext`. However, only + // Wasm-defined functions have a `VMContext`. + // In the case of Host-defined functions `VMContext` is whatever environment + // the user want to attach to the function. + let vmctx = env as *mut _ as *mut VMContext; let func_type = func.ty(); let signature = store.engine().register_signature(&func_type); Self { store: store.clone(), owned_by_store: true, - inner: InnerFunc::Host(HostFunc { - // func - }), + definition: FunctionDefinition::Host, exported: ExportFunction { address, + kind: VMFunctionKind::Static, vmctx, signature, }, @@ -584,7 +635,7 @@ impl Function { fn call_wasm( &self, - func: &WasmFunc, + func: &WasmFunctionDefinition, params: &[Val], results: &mut [Val], ) -> Result<(), RuntimeError> { @@ -675,8 +726,8 @@ impl Function { /// call the trampoline. pub fn call(&self, params: &[Val]) -> Result, RuntimeError> { let mut results = vec![Val::null(); self.result_arity()]; - match &self.inner { - InnerFunc::Wasm(wasm) => { + match &self.definition { + FunctionDefinition::Wasm(wasm) => { self.call_wasm(&wasm, params, &mut results)?; } _ => {} // _ => unimplemented!("The host is unimplemented"), @@ -685,11 +736,14 @@ impl Function { } pub(crate) fn from_export(store: &Store, wasmer_export: ExportFunction) -> Self { - let trampoline = store.engine().trampoline(wasmer_export.signature).unwrap(); + let trampoline = store + .engine() + .function_call_trampoline(wasmer_export.signature) + .unwrap(); Self { store: store.clone(), owned_by_store: false, - inner: InnerFunc::Wasm(WasmFunc { trampoline }), + definition: FunctionDefinition::Wasm(WasmFunctionDefinition { trampoline }), exported: wasmer_export, } } @@ -720,3 +774,113 @@ impl std::fmt::Debug for Function { Ok(()) } } + +/// This trait is one that all dynamic funcitons must fulfill. +trait VMDynamicFunction { + fn call(&self, args: &[Val]) -> Result, RuntimeError>; +} + +struct VMDynamicFunctionWithoutEnv { + func: Box Result, RuntimeError> + 'static>, +} + +impl VMDynamicFunction for VMDynamicFunctionWithoutEnv { + fn call(&self, args: &[Val]) -> Result, RuntimeError> { + (*self.func)(&args) + } +} + +struct VMDynamicFunctionWithEnv +where + Env: Sized, +{ + func: Box Result, RuntimeError> + 'static>, + env: *mut Env, +} + +impl VMDynamicFunction for VMDynamicFunctionWithEnv +where + Env: Sized, +{ + fn call(&self, args: &[Val]) -> Result, RuntimeError> { + unsafe { (*self.func)(&mut *self.env, &args) } + } +} + +trait VMDynamicFunctionImportCall { + fn from_context(ctx: T) -> Self; + fn address_ptr() -> *const VMFunctionBody; + unsafe fn func_wrapper( + &self, + caller_vmctx: *mut VMContext, + sig_index: SignatureIndex, + values_vec: *mut i128, + ); +} + +impl VMDynamicFunctionImportCall for VMDynamicFunctionImportContext { + fn from_context(ctx: T) -> Self { + Self { + address: Self::address_ptr(), + ctx, + } + } + + fn address_ptr() -> *const VMFunctionBody { + Self::func_wrapper as *const () as *const VMFunctionBody + } + + // This function wraps our func, to make it compatible with the + // reverse trampoline signature + unsafe fn func_wrapper( + // Note: we use the trick that the first param to this function is the `VMDynamicFunctionImportContext` + // itself, so rather than doing `dynamic_ctx: &VMDynamicFunctionImportContext`, we simplify it a bit + &self, + caller_vmctx: *mut VMContext, + sig_index: SignatureIndex, + values_vec: *mut i128, + ) { + use std::panic::{self, AssertUnwindSafe}; + let result = panic::catch_unwind(AssertUnwindSafe(|| { + // This is actually safe, since right now the function signature + // receives two contexts: + // 1. `vmctx`: the context associated to where the function is defined. + // It will be `VMContext` in case is defined in Wasm, and a custom + // `Env` in case is host defined. + // 2. `caller_vmctx`: the context associated to whoever is calling that function. + // + // Because this code will only be reached when calling from wasm to host, we + // can assure the callee_vmctx is indeed a VMContext, and hence is completely + // safe to get a handle from it. + let handle = InstanceHandle::from_vmctx(caller_vmctx); + let module = handle.module_ref(); + let func_ty = &module.signatures[sig_index]; + let mut args = Vec::with_capacity(func_ty.params().len()); + for (i, ty) in func_ty.params().iter().enumerate() { + args.push(Val::read_value_from(values_vec.add(i), *ty)); + } + let returns = self.ctx.call(&args)?; + + // We need to dynamically check that the returns + // match the expected types, as well as expected length. + let return_types = returns.iter().map(|ret| ret.ty()).collect::>(); + if return_types != func_ty.results() { + return Err(RuntimeError::new(format!( + "Dynamic function returned wrong signature. Expected {:?} but got {:?}", + func_ty.results(), + return_types + ))); + } + for (i, ret) in returns.iter().enumerate() { + ret.write_value_to(values_vec.add(i)); + } + Ok(()) + })); + + match result { + Ok(Ok(())) => {} + Ok(Err(trap)) => wasmer_runtime::raise_user_trap(Box::new(trap)), + Err(panic) => wasmer_runtime::resume_panic(panic), + } + } +} diff --git a/lib/api/src/ptr.rs b/lib/api/src/ptr.rs index fe4f5f75a..e48feda9c 100644 --- a/lib/api/src/ptr.rs +++ b/lib/api/src/ptr.rs @@ -272,7 +272,7 @@ mod test { // create a memory let store = Store::default(); let memory_descriptor = MemoryType::new(1, Some(1), false); - let memory = Memory::new(&store, memory_descriptor); + let memory = Memory::new(&store, memory_descriptor).unwrap(); // test that basic access works and that len = 0 works, but oob does not let start_wasm_ptr: WasmPtr = WasmPtr::new(0); diff --git a/lib/api/src/types.rs b/lib/api/src/types.rs index 6b130efae..f477362a6 100644 --- a/lib/api/src/types.rs +++ b/lib/api/src/types.rs @@ -64,6 +64,9 @@ impl ValAnyFunc for Val { let export = wasmer_runtime::ExportFunction { address: item.func_ptr, signature: item.type_index, + // All functions in tables are already Static (as dynamic functions + // are converted to use the trampolines with static signatures). + kind: wasmer_runtime::VMFunctionKind::Static, vmctx: item.vmctx, }; let f = Function::from_export(store, export); diff --git a/lib/compiler-cranelift/src/compiler.rs b/lib/compiler-cranelift/src/compiler.rs index c4b67edd3..625161649 100644 --- a/lib/compiler-cranelift/src/compiler.rs +++ b/lib/compiler-cranelift/src/compiler.rs @@ -4,7 +4,9 @@ use crate::address_map::get_function_address_map; use crate::config::CraneliftConfig; use crate::func_environ::{get_func_name, FuncEnvironment}; use crate::sink::{RelocSink, TrapSink}; -use crate::trampoline::{make_wasm_trampoline, FunctionBuilderContext}; +use crate::trampoline::{ + make_trampoline_dynamic_function, make_trampoline_function_call, FunctionBuilderContext, +}; use crate::translator::{ compiled_function_unwind_info, signature_to_cranelift_ir, transform_jump_table, FuncTranslator, }; @@ -14,7 +16,8 @@ use cranelift_codegen::{binemit, isa, Context}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use wasm_common::entity::PrimaryMap; use wasm_common::{ - Features, FunctionType, LocalFunctionIndex, MemoryIndex, SignatureIndex, TableIndex, + Features, FunctionIndex, FunctionType, LocalFunctionIndex, MemoryIndex, SignatureIndex, + TableIndex, }; use wasmer_compiler::CompileError; use wasmer_compiler::{ @@ -155,15 +158,37 @@ impl Compiler for CraneliftCompiler { Ok(Compilation::new(functions, custom_sections)) } - fn compile_wasm_trampolines( + fn compile_function_call_trampolines( &self, signatures: &[FunctionType], ) -> Result, CompileError> { signatures .par_iter() .map_init(FunctionBuilderContext::new, |mut cx, sig| { - make_wasm_trampoline(&*self.isa, &mut cx, sig, std::mem::size_of::()) + make_trampoline_function_call(&*self.isa, &mut cx, sig) }) .collect::, CompileError>>() } + + fn compile_dynamic_function_trampolines( + &self, + module: &Module, + ) -> Result, CompileError> { + use wasmer_runtime::VMOffsets; + let isa = self.isa(); + let frontend_config = isa.frontend_config(); + let offsets = VMOffsets::new(frontend_config.pointer_bytes(), module); + Ok(module + .functions + .values() + .take(module.num_imported_funcs) + .collect::>() + .par_iter() + .map_init(FunctionBuilderContext::new, |mut cx, sig_index| { + make_trampoline_dynamic_function(&*self.isa, &module, &offsets, &mut cx, &sig_index) + }) + .collect::, CompileError>>()? + .into_iter() + .collect::>()) + } } diff --git a/lib/compiler-cranelift/src/lib.rs b/lib/compiler-cranelift/src/lib.rs index 3147e0b88..87f35bafd 100644 --- a/lib/compiler-cranelift/src/lib.rs +++ b/lib/compiler-cranelift/src/lib.rs @@ -58,7 +58,7 @@ pub use crate::compiler::CraneliftCompiler; pub use crate::config::CraneliftConfig; pub use crate::debug::{FrameLayout, FrameLayoutChange, FrameLayouts}; pub use crate::debug::{ModuleMemoryOffset, ModuleVmctxInfo, ValueLabelsRanges}; -pub use crate::trampoline::make_wasm_trampoline; +pub use crate::trampoline::make_trampoline_function_call; /// Version number of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/lib/compiler-cranelift/src/trampoline/dynamic_function.rs b/lib/compiler-cranelift/src/trampoline/dynamic_function.rs new file mode 100644 index 000000000..6959c93df --- /dev/null +++ b/lib/compiler-cranelift/src/trampoline/dynamic_function.rs @@ -0,0 +1,146 @@ +//! A trampoline generator for calling dynamic host functions from Wasm. + +use super::binemit::TrampolineRelocSink; +use crate::translator::{compiled_function_unwind_info, signature_to_cranelift_ir}; +use cranelift_codegen::ir::{ + types, ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind, +}; +use cranelift_codegen::isa::TargetIsa; +use cranelift_codegen::print_errors::pretty_error; +use cranelift_codegen::Context; +use cranelift_codegen::{binemit, ir}; +use std::cmp; +use std::mem; + +use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; +use wasm_common::entity::EntityRef; +use wasm_common::SignatureIndex; +use wasmer_compiler::{CompileError, FunctionBody}; +use wasmer_runtime::{Module, VMOffsets}; + +/// Create a trampoline for invoking a WebAssembly function. +pub fn make_trampoline_dynamic_function( + isa: &dyn TargetIsa, + module: &Module, + offsets: &VMOffsets, + fn_builder_ctx: &mut FunctionBuilderContext, + sig_index: &SignatureIndex, +) -> Result { + let func_type = &module.signatures[*sig_index]; + let pointer_type = isa.pointer_type(); + let frontend_config = isa.frontend_config(); + let signature = signature_to_cranelift_ir(func_type, &frontend_config); + let mut stub_sig = ir::Signature::new(frontend_config.default_call_conv); + // Add the caller `vmctx` parameter. + stub_sig.params.push(ir::AbiParam::special( + pointer_type, + ir::ArgumentPurpose::VMContext, + )); + + // Add the caller/callee `vmctx` parameter. + stub_sig.params.push(ir::AbiParam::new(pointer_type)); + + // Add the `sig_index` parameter. + stub_sig.params.push(ir::AbiParam::new(types::I32)); + + // Add the `values_vec` parameter. + stub_sig.params.push(ir::AbiParam::new(pointer_type)); + + // Compute the size of the values vector. The vmctx and caller vmctx are passed separately. + let value_size = mem::size_of::(); + let values_vec_len = + (value_size * cmp::max(signature.params.len() - 2, signature.returns.len())) as u32; + + let mut context = Context::new(); + context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone()); + + let ss = context.func.create_stack_slot(StackSlotData::new( + StackSlotKind::ExplicitSlot, + values_vec_len, + )); + + { + let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx); + let block0 = builder.create_block(); + + builder.append_block_params_for_function_params(block0); + builder.switch_to_block(block0); + builder.seal_block(block0); + + let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0); + let mflags = MemFlags::trusted(); + // We only get the non-vmctx arguments + for i in 2..signature.params.len() { + let val = builder.func.dfg.block_params(block0)[i]; + builder.ins().store( + mflags, + val, + values_vec_ptr_val, + ((i - 2) * value_size) as i32, + ); + } + + let block_params = builder.func.dfg.block_params(block0); + let vmctx_ptr_val = block_params[0]; + let caller_vmctx_ptr_val = block_params[1]; + + // Get the signature index + let caller_sig_id = builder.ins().iconst(types::I32, sig_index.index() as i64); + + let callee_args = vec![ + vmctx_ptr_val, + caller_vmctx_ptr_val, + caller_sig_id, + values_vec_ptr_val, + ]; + + let new_sig = builder.import_signature(stub_sig); + + let mem_flags = ir::MemFlags::trusted(); + let callee_value = builder.ins().load( + pointer_type, + mem_flags, + vmctx_ptr_val, + offsets.vmdynamicfunction_import_context_address() as i32, + ); + + builder + .ins() + .call_indirect(new_sig, callee_value, &callee_args); + + let mflags = MemFlags::trusted(); + let mut results = Vec::new(); + for (i, r) in signature.returns.iter().enumerate() { + let load = builder.ins().load( + r.value_type, + mflags, + values_vec_ptr_val, + (i * value_size) as i32, + ); + results.push(load); + } + builder.ins().return_(&results); + builder.finalize() + } + + let mut code_buf = Vec::new(); + let mut reloc_sink = TrampolineRelocSink {}; + let mut trap_sink = binemit::NullTrapSink {}; + let mut stackmap_sink = binemit::NullStackmapSink {}; + context + .compile_and_emit( + isa, + &mut code_buf, + &mut reloc_sink, + &mut trap_sink, + &mut stackmap_sink, + ) + .map_err(|error| CompileError::Codegen(pretty_error(&context.func, Some(isa), error)))?; + + let unwind_info = compiled_function_unwind_info(isa, &context); + + Ok(FunctionBody { + body: code_buf, + unwind_info, + }) +} diff --git a/lib/compiler-cranelift/src/trampoline/wasm.rs b/lib/compiler-cranelift/src/trampoline/function_call.rs similarity index 97% rename from lib/compiler-cranelift/src/trampoline/wasm.rs rename to lib/compiler-cranelift/src/trampoline/function_call.rs index 5c5bd2f71..4c62a5833 100644 --- a/lib/compiler-cranelift/src/trampoline/wasm.rs +++ b/lib/compiler-cranelift/src/trampoline/function_call.rs @@ -15,15 +15,15 @@ use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::Context; use cranelift_codegen::{binemit, ir}; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; +use std::mem; use wasm_common::FunctionType; use wasmer_compiler::{CompileError, FunctionBody}; /// Create a trampoline for invoking a WebAssembly function. -pub fn make_wasm_trampoline( +pub fn make_trampoline_function_call( isa: &dyn TargetIsa, fn_builder_ctx: &mut FunctionBuilderContext, func_type: &FunctionType, - value_size: usize, ) -> Result { let pointer_type = isa.pointer_type(); let frontend_config = isa.frontend_config(); @@ -49,6 +49,7 @@ pub fn make_wasm_trampoline( context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig); context.func.collect_frame_layout_info(); + let value_size = mem::size_of::(); { let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx); let block0 = builder.create_block(); diff --git a/lib/compiler-cranelift/src/trampoline/mod.rs b/lib/compiler-cranelift/src/trampoline/mod.rs index 5658c5962..9fe8e5b5f 100644 --- a/lib/compiler-cranelift/src/trampoline/mod.rs +++ b/lib/compiler-cranelift/src/trampoline/mod.rs @@ -1,10 +1,10 @@ #![allow(missing_docs)] -// mod host; -mod wasm; +mod dynamic_function; +mod function_call; -// pub use host::make_host_trampoline; -pub use self::wasm::make_wasm_trampoline; +pub use self::dynamic_function::make_trampoline_dynamic_function; +pub use self::function_call::make_trampoline_function_call; // TODO: Delete pub mod ir { diff --git a/lib/compiler-llvm/src/compiler.rs b/lib/compiler-llvm/src/compiler.rs index 1e57b9892..b305fa3d5 100644 --- a/lib/compiler-llvm/src/compiler.rs +++ b/lib/compiler-llvm/src/compiler.rs @@ -82,6 +82,7 @@ impl Compiler for LLVMCompiler { .cloned() .unwrap_or_else(|| format!("fn{}", func_index.index())); } + let mut module_custom_sections = PrimaryMap::new(); let mut functions = function_body_inputs .into_iter() .collect::)>>() @@ -101,46 +102,41 @@ impl Compiler for LLVMCompiler { }) .collect::, CompileError>>()? .into_iter() - .map(|(mut function, local_relocations, custom_sections)| { - /// We collect the sections data - for (local_idx, custom_section) in custom_sections.iter().enumerate() { - let local_idx = local_idx as u32; - // TODO: these section numbers are potentially wrong, if there's - // no Read and only a ReadExecute then ReadExecute is 0. - let (ref mut section, section_num) = match &custom_section.protection { - CustomSectionProtection::Read => { - (&mut readonly_section, SectionIndex::from_u32(0)) - } - }; - let offset = section.bytes.len() as i64; - section.bytes.append(&custom_section.bytes); - // TODO: we're needlessly rescanning the whole list. - for local_relocation in &local_relocations { - if local_relocation.local_section_index == local_idx { - used_readonly_section = true; - function.relocations.push(Relocation { - kind: local_relocation.kind, - reloc_target: RelocationTarget::CustomSection(section_num), - offset: local_relocation.offset, - addend: local_relocation.addend + offset, - }); + .map(|(mut compiled_function, mut function_custom_sections)| { + let first_section = module_custom_sections.len() as u32; + for (_, custom_section) in function_custom_sections.iter() { + // TODO: remove this call to clone() + let mut custom_section = custom_section.clone(); + for mut reloc in &mut custom_section.relocations { + match reloc.reloc_target { + RelocationTarget::CustomSection(index) => { + reloc.reloc_target = RelocationTarget::CustomSection( + SectionIndex::from_u32(first_section + index.as_u32()), + ) + } + _ => {} } } + module_custom_sections.push(custom_section); } - Ok(function) + for mut reloc in &mut compiled_function.relocations { + match reloc.reloc_target { + RelocationTarget::CustomSection(index) => { + reloc.reloc_target = RelocationTarget::CustomSection( + SectionIndex::from_u32(first_section + index.as_u32()), + ) + } + _ => {} + } + } + compiled_function }) - .collect::, CompileError>>()? - .into_iter() .collect::>(); - let mut custom_sections = PrimaryMap::new(); - if used_readonly_section { - custom_sections.push(readonly_section); - } - Ok(Compilation::new(functions, custom_sections)) + Ok(Compilation::new(functions, module_custom_sections)) } - fn compile_wasm_trampolines( + fn compile_function_call_trampolines( &self, signatures: &[FunctionType], ) -> Result, CompileError> { @@ -151,4 +147,12 @@ impl Compiler for LLVMCompiler { }) .collect::, CompileError>>() } + + fn compile_dynamic_function_trampolines( + &self, + module: &Module, + ) -> Result, CompileError> { + Ok(PrimaryMap::new()) + // unimplemented!("Dynamic funciton trampolines not yet implemented"); + } } diff --git a/lib/compiler-llvm/src/config.rs b/lib/compiler-llvm/src/config.rs index d95b5f282..581902aba 100644 --- a/lib/compiler-llvm/src/config.rs +++ b/lib/compiler-llvm/src/config.rs @@ -50,10 +50,22 @@ impl LLVMConfig { // Override the default multi-value switch features.multi_value = false; + let operating_system = + if target.triple().operating_system == wasmer_compiler::OperatingSystem::Darwin { + // LLVM detects static relocation + darwin + 64-bit and + // force-enables PIC because MachO doesn't support that + // combination. They don't check whether they're targeting + // MachO, they check whether the OS is set to Darwin. + // + // Since both linux and darwin use SysV ABI, this should work. + wasmer_compiler::OperatingSystem::Linux + } else { + target.triple().operating_system + }; let triple = Triple { architecture: target.triple().architecture, vendor: target.triple().vendor.clone(), - operating_system: target.triple().operating_system, + operating_system, environment: target.triple().environment, binary_format: target_lexicon::BinaryFormat::Elf, }; diff --git a/lib/compiler-llvm/src/translator/code.rs b/lib/compiler-llvm/src/translator/code.rs index 216ac0e2e..887f495eb 100644 --- a/lib/compiler-llvm/src/translator/code.rs +++ b/lib/compiler-llvm/src/translator/code.rs @@ -29,10 +29,12 @@ use inkwell::{ }; use smallvec::SmallVec; use std::any::Any; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::convert::TryFrom; +use std::num::TryFromIntError; use crate::config::LLVMConfig; -use wasm_common::entity::{EntityRef, PrimaryMap, SecondaryMap}; +use wasm_common::entity::{PrimaryMap, SecondaryMap}; use wasm_common::{ FunctionIndex, FunctionType, GlobalIndex, LocalFunctionIndex, MemoryIndex, MemoryType, Mutability, SignatureIndex, TableIndex, Type, @@ -40,9 +42,9 @@ use wasm_common::{ use wasmer_compiler::wasmparser::{self, BinaryReader, MemoryImmediate, Operator}; use wasmer_compiler::{ to_wasm_error, wasm_unsupported, Addend, CodeOffset, CompileError, CompiledFunction, - CompiledFunctionFrameInfo, CustomSection, CustomSectionProtection, FunctionAddressMap, - FunctionBody, FunctionBodyData, InstructionAddressMap, Relocation, RelocationKind, - RelocationTarget, SectionBody, SourceLoc, WasmResult, + CompiledFunctionFrameInfo, CustomSection, CustomSectionProtection, CustomSections, + FunctionAddressMap, FunctionBody, FunctionBodyData, InstructionAddressMap, Relocation, + RelocationKind, RelocationTarget, SectionBody, SectionIndex, SourceLoc, WasmResult, }; use wasmer_runtime::libcalls::LibCall; use wasmer_runtime::Module as WasmerCompilerModule; @@ -52,6 +54,30 @@ use wasmer_runtime::{MemoryPlan, MemoryStyle, TablePlan, VMBuiltinFunctionIndex, use std::fs; use std::io::Write; +use wasm_common::entity::entity_impl; +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +pub struct ElfSectionIndex(u32); +entity_impl!(ElfSectionIndex); +impl ElfSectionIndex { + pub fn is_undef(&self) -> bool { + self.as_u32() == goblin::elf::section_header::SHN_UNDEF + } + + pub fn from_usize(value: usize) -> Result { + match u32::try_from(value) { + Err(_) => Err(CompileError::Codegen(format!( + "elf section index {} does not fit in 32 bits", + value + ))), + Ok(value) => Ok(ElfSectionIndex::from_u32(value)), + } + } + + pub fn as_usize(&self) -> usize { + self.as_u32() as usize + } +} + // TODO fn wptype_to_type(ty: wasmparser::Type) -> WasmResult { match ty { @@ -84,15 +110,6 @@ fn const_zero<'ctx>(ty: BasicTypeEnum<'ctx>) -> BasicValueEnum<'ctx> { } } -// Relocation against a per-function section. -#[derive(Debug)] -pub struct LocalRelocation { - pub kind: RelocationKind, - pub local_section_index: u32, - pub offset: CodeOffset, - pub addend: Addend, -} - impl FuncTranslator { pub fn new() -> Self { Self { @@ -109,7 +126,7 @@ impl FuncTranslator { memory_plans: &PrimaryMap, table_plans: &PrimaryMap, func_names: &SecondaryMap, - ) -> Result<(CompiledFunction, Vec, Vec), CompileError> { + ) -> Result<(CompiledFunction, CustomSections), CompileError> { let func_index = wasm_module.func_index(*local_func_index); let func_name = &func_names[func_index]; let module_name = match wasm_module.name.as_ref() { @@ -135,7 +152,7 @@ impl FuncTranslator { // TODO: figure out how many bytes long vmctx is, and mark it dereferenceable. (no need to mark it nonnull once we do this.) // TODO: mark vmctx nofree func.set_personality_function(intrinsics.personality); - func.as_global_value().set_section("wasmer_function"); + func.as_global_value().set_section(".wasmer_function"); let entry = self.ctx.append_basic_block(func, "entry"); let start_of_code = self.ctx.append_basic_block(func, "start_of_code"); @@ -165,7 +182,9 @@ impl FuncTranslator { for idx in 0..wasm_fn_type.params().len() { let ty = wasm_fn_type.params()[idx]; let ty = type_to_llvm(&intrinsics, ty); - let value = func.get_nth_param((idx + 2) as u32).unwrap(); + let value = func + .get_nth_param((idx as u32).checked_add(2).unwrap()) + .unwrap(); // TODO: don't interleave allocas and stores. let alloca = cache_builder.build_alloca(ty, "param"); cache_builder.build_store(alloca, value); @@ -335,74 +354,118 @@ impl FuncTranslator { Some(name.unwrap()) }; - let wasmer_function_idx = elf + // Build up a mapping from a section to its relocation sections. + let reloc_sections = elf.shdr_relocs.iter().fold( + HashMap::new(), + |mut map: HashMap<_, Vec<_>>, (section_index, reloc_section)| { + let target_section = elf.section_headers[*section_index].sh_info as usize; + let target_section = ElfSectionIndex::from_usize(target_section).unwrap(); + map.entry(target_section).or_default().push(reloc_section); + map + }, + ); + + let mut visited: HashSet = HashSet::new(); + let mut worklist: Vec = Vec::new(); + let mut section_targets: HashMap = HashMap::new(); + + let wasmer_function_index = elf .section_headers .iter() .enumerate() - .filter(|(_, section)| get_section_name(section) == Some("wasmer_function")) - .map(|(idx, _)| idx) - .take(1) + .filter(|(_, section)| get_section_name(section) == Some(".wasmer_function")) + .map(|(index, _)| index) .collect::>(); - // TODO: handle errors here instead of asserting. - assert!(wasmer_function_idx.len() == 1); - let wasmer_function_idx = wasmer_function_idx[0]; + if wasmer_function_index.len() != 1 { + return Err(CompileError::Codegen(format!( + "found {} sections named .wasmer_function", + wasmer_function_index.len() + ))); + } + let wasmer_function_index = wasmer_function_index[0]; + let wasmer_function_index = ElfSectionIndex::from_usize(wasmer_function_index)?; - let bytes = elf.section_headers[wasmer_function_idx].file_range(); - let bytes = mem_buf_slice[bytes.start..bytes.end].to_vec(); + let mut section_to_custom_section = HashMap::new(); - let mut relocations = vec![]; - let mut local_relocations = vec![]; - let mut required_custom_sections = HashMap::new(); + section_targets.insert( + wasmer_function_index, + RelocationTarget::LocalFunc(*local_func_index), + ); - for (section_index, reloc_section) in &elf.shdr_relocs { - let section_name = get_section_name(&elf.section_headers[*section_index]); - if section_name == Some(".rel.rodata") || section_name == Some(".rela.rodata") { - return Err(CompileError::Codegen( - "jump tables not yet implemented".to_string(), - )); - } - if section_name != Some(".relawasmer_function") - && section_name != Some(".relwasmer_function") + let mut next_custom_section: u32 = 0; + let mut elf_section_to_target = |elf_section_index: ElfSectionIndex| { + *section_targets.entry(elf_section_index).or_insert_with(|| { + let next = SectionIndex::from_u32(next_custom_section); + section_to_custom_section.insert(elf_section_index, next); + let target = RelocationTarget::CustomSection(next); + next_custom_section += 1; + target + }) + }; + + let section_bytes = |elf_section_index: ElfSectionIndex| { + let elf_section_index = elf_section_index.as_usize(); + let byte_range = elf.section_headers[elf_section_index].file_range(); + mem_buf_slice[byte_range.start..byte_range.end].to_vec() + }; + + // From elf section index to list of Relocations. Although we use a Vec, + // the order of relocations is not important. + let mut relocations: HashMap> = HashMap::new(); + + // Each iteration of this loop pulls a section and the relocations + // relocations that apply to it. We begin with the ".wasmer_function" + // section, and then parse all relocation sections that apply to that + // section. Those relocations may refer to additional sections which we + // then add to the worklist until we've visited the closure of + // everything needed to run the code in ".wasmer_function". + // + // `worklist` is the list of sections we have yet to visit. It never + // contains any duplicates or sections we've already visited. `visited` + // contains all the sections we've ever added to the worklist in a set + // so that we can quickly check whether a section is new before adding + // it to worklist. `section_to_custom_section` is filled in with all + // the sections we want to include. + worklist.push(wasmer_function_index); + visited.insert(wasmer_function_index); + while let Some(section_index) = worklist.pop() { + for reloc in reloc_sections + .get(§ion_index) + .iter() + .flat_map(|inner| inner.iter().flat_map(|inner2| inner2.iter())) { - continue; - } - for reloc in reloc_section.iter() { let kind = match reloc.r_type { // TODO: these constants are not per-arch, we'll need to // make the whole match per-arch. goblin::elf::reloc::R_X86_64_64 => RelocationKind::Abs8, - _ => unimplemented!("unknown relocation {}", reloc.r_type), + _ => { + return Err(CompileError::Codegen(format!( + "unknown ELF relocation {}", + reloc.r_type + ))); + } }; let offset = reloc.r_offset as u32; let addend = reloc.r_addend.unwrap_or(0); let target = reloc.r_sym; // TODO: error handling - let target = elf.syms.get(target).unwrap(); - if target.st_type() == goblin::elf::sym::STT_SECTION { - let len = required_custom_sections.len(); - let entry = required_custom_sections.entry(target.st_shndx); - let local_section_index = *entry.or_insert(len) as _; - local_relocations.push(LocalRelocation { - kind, - local_section_index, - offset, - addend, - }); - } else if target.st_type() == goblin::elf::sym::STT_FUNC - && target.st_shndx == wasmer_function_idx + let elf_target = elf.syms.get(target).unwrap(); + let elf_target_section = ElfSectionIndex::from_usize(elf_target.st_shndx)?; + let reloc_target = if elf_target.st_type() == goblin::elf::sym::STT_SECTION { + if visited.insert(elf_target_section) { + worklist.push(elf_target_section); + } + elf_section_to_target(elf_target_section) + } else if elf_target.st_type() == goblin::elf::sym::STT_FUNC + && elf_target_section == wasmer_function_index { // This is a function referencing its own byte stream. - relocations.push(Relocation { - kind, - reloc_target: RelocationTarget::LocalFunc(*local_func_index), - offset, - addend, - }); - } else if target.st_type() == goblin::elf::sym::STT_NOTYPE - && target.st_shndx == goblin::elf::section_header::SHN_UNDEF as _ + RelocationTarget::LocalFunc(*local_func_index) + } else if elf_target.st_type() == goblin::elf::sym::STT_NOTYPE + && elf_target_section.is_undef() { // Not defined in this .o file. Maybe another local function? - let name = target.st_name; + let name = elf_target.st_name; let name = elf.strtab.get(name).unwrap().unwrap(); if let Some((index, _)) = func_names.iter().find(|(_, func_name)| *func_name == name) @@ -410,70 +473,78 @@ impl FuncTranslator { let local_index = wasm_module .local_func_index(index) .expect("Relocation to non-local function"); - relocations.push(Relocation { - kind, - reloc_target: RelocationTarget::LocalFunc(local_index), - offset, - addend, - }); + RelocationTarget::LocalFunc(local_index) // Maybe a libcall then? } else if let Some(libcall) = libcalls.get(name) { - relocations.push(Relocation { - kind, - reloc_target: RelocationTarget::LibCall(*libcall), - offset, - addend, - }); + RelocationTarget::LibCall(*libcall) } else { unimplemented!("reference to unknown symbol {}", name); } } else { unimplemented!("unknown relocation {:?} with target {:?}", reloc, target); - } + }; + relocations + .entry(section_index) + .or_default() + .push(Relocation { + kind, + reloc_target, + offset, + addend, + }); } } - let mut custom_sections = vec![]; - custom_sections.resize( - required_custom_sections.len(), - CustomSection { - protection: CustomSectionProtection::Read, - bytes: SectionBody::default(), - relocations: vec![], - }, - ); - for (section_idx, local_section_idx) in required_custom_sections { - let bytes = elf.section_headers[section_idx as usize].file_range(); - let bytes = &mem_buf_slice[bytes.start..bytes.end]; - custom_sections[local_section_idx].bytes.extend(bytes); - } + let mut custom_sections = section_to_custom_section + .iter() + .map(|(elf_section_index, custom_section_index)| { + ( + custom_section_index, + CustomSection { + protection: CustomSectionProtection::Read, + bytes: SectionBody::new_with_vec(section_bytes(*elf_section_index)), + relocations: relocations + .remove_entry(elf_section_index) + .map_or(vec![], |(_, v)| v), + }, + ) + }) + .collect::>(); + custom_sections.sort_unstable_by_key(|a| a.0); + let custom_sections = custom_sections + .into_iter() + .map(|(_, v)| v) + .collect::>(); + + let function_body = FunctionBody { + body: section_bytes(wasmer_function_index), + unwind_info: None, + }; let address_map = FunctionAddressMap { instructions: vec![InstructionAddressMap { srcloc: SourceLoc::default(), code_offset: 0, - code_len: bytes.len(), + code_len: function_body.body.len(), }], start_srcloc: SourceLoc::default(), end_srcloc: SourceLoc::default(), body_offset: 0, - body_len: bytes.len(), + body_len: function_body.body.len(), }; Ok(( CompiledFunction { - body: FunctionBody { - body: bytes, - unwind_info: None, - }, + body: function_body, jt_offsets: SecondaryMap::new(), - relocations, + relocations: relocations + .remove_entry(&wasmer_function_index) + .map_or(vec![], |(_, v)| v), frame_info: CompiledFunctionFrameInfo { address_map, traps: vec![], }, }, - local_relocations, custom_sections, )) } @@ -2156,96 +2227,34 @@ impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> { Operator::GlobalGet { global_index } => { let global_index = GlobalIndex::from_u32(global_index); - let global_type = module.globals[global_index]; - let global_value_type = global_type.ty; - - // TODO: cache loads of const globals. - let _global_mutability = global_type.mutability; - - let global_ptr = - if let Some(local_global_index) = module.local_global_index(global_index) { - let offset = self.vmoffsets.vmctx_vmglobal_definition(local_global_index); - let offset = intrinsics.i32_ty.const_int(offset.into(), false); - unsafe { builder.build_gep(*vmctx, &[offset], "") } - } else { - let offset = self.vmoffsets.vmctx_vmglobal_import(global_index); - let offset = intrinsics.i32_ty.const_int(offset.into(), false); - let global_ptr_ptr = unsafe { builder.build_gep(*vmctx, &[offset], "") }; - let global_ptr_ptr = builder - .build_bitcast(global_ptr_ptr, intrinsics.i8_ptr_ty, "") - .into_pointer_value(); - let global_ptr = builder.build_load(global_ptr_ptr, ""); - builder - .build_bitcast(global_ptr, intrinsics.i8_ptr_ty, "") - .into_pointer_value() - }; - let global_ptr = builder - .build_bitcast( - global_ptr, - type_to_llvm_ptr(&intrinsics, global_value_type), - "", - ) - .into_pointer_value(); - let value = builder.build_load(global_ptr, ""); - // TODO: add TBAA info. - self.state.push1(value); + match ctx.global(global_index, intrinsics) { + GlobalCache::Const { value } => { + self.state.push1(value); + } + GlobalCache::Mut { ptr_to_value } => { + let value = builder.build_load(ptr_to_value, ""); + // TODO: tbaa + self.state.push1(value); + } + } } Operator::GlobalSet { global_index } => { let global_index = GlobalIndex::from_u32(global_index); - let global_type = module.globals[global_index]; - let global_value_type = global_type.ty; - - // Note that we don't check mutability, assuming that's already - // been checked by some other verifier. - - let global_ptr = - if let Some(local_global_index) = module.local_global_index(global_index) { - let offset = self.vmoffsets.vmctx_vmglobal_definition(local_global_index); - let offset = intrinsics.i32_ty.const_int(offset.into(), false); - unsafe { builder.build_gep(*vmctx, &[offset], "") } - } else { - let offset = self.vmoffsets.vmctx_vmglobal_import(global_index); - let offset = intrinsics.i32_ty.const_int(offset.into(), false); - let global_ptr_ptr = unsafe { builder.build_gep(*vmctx, &[offset], "") }; - let global_ptr_ptr = builder - .build_bitcast(global_ptr_ptr, intrinsics.i8_ptr_ty, "") - .into_pointer_value(); - builder.build_load(global_ptr_ptr, "").into_pointer_value() - }; - let global_ptr = builder - .build_bitcast( - global_ptr, - type_to_llvm_ptr(&intrinsics, global_value_type), - "", - ) - .into_pointer_value(); - - let (value, info) = self.state.pop1_extra()?; - let value = apply_pending_canonicalization(builder, intrinsics, value, info); - builder.build_store(global_ptr, value); - // TODO: add TBAA info - - /* - let (value, info) = self.state.pop1_extra()?; - let value = apply_pending_canonicalization(builder, intrinsics, value, info); - let index = GlobalIndex::from_u32(global_index); - let global_cache = ctx.global_cache(index, intrinsics, self.module); - match global_cache { - GlobalCache::Mut { ptr_to_value } => { - let store = builder.build_store(ptr_to_value, value); - tbaa_label( - &self.module, - intrinsics, - "global", - store, - Some(global_index), - ); - } - GlobalCache::Const { value: _ } => { - return Err(CompileError::Codegen("global is immutable".to_string())); - } - } - */ + match ctx.global(global_index, intrinsics) { + GlobalCache::Const { value } => { + return Err(CompileError::Codegen(format!( + "global.set on immutable global index {}", + global_index.as_u32() + ))) + } + GlobalCache::Mut { ptr_to_value } => { + let (value, info) = self.state.pop1_extra()?; + let value = + apply_pending_canonicalization(builder, intrinsics, value, info); + builder.build_store(ptr_to_value, value); + // TODO: tbaa + } + } } Operator::Select => { diff --git a/lib/compiler-llvm/src/translator/intrinsics.rs b/lib/compiler-llvm/src/translator/intrinsics.rs index c0baf6f23..bbe95e0cc 100644 --- a/lib/compiler-llvm/src/translator/intrinsics.rs +++ b/lib/compiler-llvm/src/translator/intrinsics.rs @@ -910,7 +910,7 @@ impl<'ctx, 'a> CtxType<'ctx, 'a> { }) } - pub fn table_prepare( + fn table_prepare( &mut self, table_index: TableIndex, intrinsics: &Intrinsics<'ctx>, @@ -952,6 +952,9 @@ impl<'ctx, 'a> CtxType<'ctx, 'a> { ); let ptr_to_bounds = unsafe { cache_builder.build_gep(ctx_ptr_value, &[offset], "") }; + let ptr_to_bounds = cache_builder + .build_bitcast(ptr_to_bounds, intrinsics.i32_ptr_ty, "") + .into_pointer_value(); (ptr_to_base_ptr, ptr_to_bounds) } else { let offset = intrinsics.i64_ty.const_int( @@ -989,6 +992,9 @@ impl<'ctx, 'a> CtxType<'ctx, 'a> { .const_int(offsets.vmtable_definition_current_elements().into(), false); let ptr_to_bounds = unsafe { cache_builder.build_gep(definition_ptr, &[offset], "") }; + let ptr_to_bounds = cache_builder + .build_bitcast(ptr_to_bounds, intrinsics.i32_ptr_ty, "") + .into_pointer_value(); (ptr_to_base_ptr, ptr_to_bounds) }; TableCache { @@ -1008,10 +1014,14 @@ impl<'ctx, 'a> CtxType<'ctx, 'a> { builder: &Builder<'ctx>, ) -> (PointerValue<'ctx>, IntValue<'ctx>) { let (ptr_to_base_ptr, ptr_to_bounds) = self.table_prepare(index, intrinsics, module); - let base_ptr = builder + let base_ptr = self + .cache_builder .build_load(ptr_to_base_ptr, "base_ptr") .into_pointer_value(); - let bounds = builder.build_load(ptr_to_bounds, "bounds").into_int_value(); + let bounds = self + .cache_builder + .build_load(ptr_to_bounds, "bounds") + .into_int_value(); tbaa_label( module, intrinsics, @@ -1078,106 +1088,59 @@ impl<'ctx, 'a> CtxType<'ctx, 'a> { }) } - pub fn global_cache( + pub fn global( &mut self, index: GlobalIndex, intrinsics: &Intrinsics<'ctx>, - module: &Module<'ctx>, ) -> GlobalCache<'ctx> { - let (cached_globals, ctx_ptr_value, wasm_module, cache_builder, offsets) = ( + let (cached_globals, wasm_module, ctx_ptr_value, cache_builder, offsets) = ( &mut self.cached_globals, - self.ctx_ptr_value, self.wasm_module, + self.ctx_ptr_value, &self.cache_builder, &self.offsets, ); *cached_globals.entry(index).or_insert_with(|| { - let (globals_array_ptr_ptr, index, mutable, wasmer_ty, field_name) = { - let desc = wasm_module.globals.get(index).unwrap(); - if let Some(_local_global_index) = wasm_module.local_global_index(index) { - ( - unsafe { - cache_builder - .build_struct_gep( - ctx_ptr_value, - offset_to_index(offsets.vmctx_globals_begin()), - "globals_array_ptr_ptr", - ) - .unwrap() - }, - index.index() as u64, - desc.mutability, - desc.ty, - "context_field_ptr_to_local_globals", - ) - } else { - ( - unsafe { - cache_builder - .build_struct_gep( - ctx_ptr_value, - offset_to_index(offsets.vmctx_imported_globals_begin()), - "globals_array_ptr_ptr", - ) - .unwrap() - }, - index.index() as u64, - desc.mutability, - desc.ty, - "context_field_ptr_to_imported_globals", - ) - } - }; + let global_type = wasm_module.globals[index]; + let global_value_type = global_type.ty; - let llvm_ptr_ty = type_to_llvm_ptr(intrinsics, wasmer_ty); - - let global_array_ptr = cache_builder - .build_load(globals_array_ptr_ptr, "global_array_ptr") - .into_pointer_value(); - tbaa_label( - module, - intrinsics, - field_name, - global_array_ptr.as_instruction_value().unwrap(), - None, - ); - let const_index = intrinsics.i32_ty.const_int(index, false); - let global_ptr_ptr = unsafe { - cache_builder.build_in_bounds_gep( - global_array_ptr, - &[const_index], - "global_ptr_ptr", - ) + let global_mutability = global_type.mutability; + let global_ptr = if let Some(local_global_index) = wasm_module.local_global_index(index) + { + let offset = offsets.vmctx_vmglobal_definition(local_global_index); + let offset = intrinsics.i32_ty.const_int(offset.into(), false); + unsafe { cache_builder.build_gep(ctx_ptr_value, &[offset], "") } + } else { + let offset = offsets.vmctx_vmglobal_import(index); + let offset = intrinsics.i32_ty.const_int(offset.into(), false); + let global_ptr_ptr = + unsafe { cache_builder.build_gep(ctx_ptr_value, &[offset], "") }; + let global_ptr_ptr = cache_builder + .build_bitcast( + global_ptr_ptr, + intrinsics.i32_ptr_ty.ptr_type(AddressSpace::Generic), + "", + ) + .into_pointer_value(); + cache_builder + .build_load(global_ptr_ptr, "") + .into_pointer_value() }; let global_ptr = cache_builder - .build_load(global_ptr_ptr, "global_ptr") + .build_bitcast( + global_ptr, + type_to_llvm_ptr(&intrinsics, global_value_type), + "", + ) .into_pointer_value(); - tbaa_label( - module, - intrinsics, - "global_ptr", - global_ptr.as_instruction_value().unwrap(), - Some(index as u32), - ); - let global_ptr_typed = - cache_builder.build_pointer_cast(global_ptr, llvm_ptr_ty, "global_ptr_typed"); - - let mutable = mutable == Mutability::Var; - if mutable { - GlobalCache::Mut { - ptr_to_value: global_ptr_typed, - } - } else { - let value = cache_builder.build_load(global_ptr_typed, "global_value"); - tbaa_label( - module, - intrinsics, - "global", - value.as_instruction_value().unwrap(), - Some(index as u32), - ); - GlobalCache::Const { value } + match global_mutability { + Mutability::Const => GlobalCache::Const { + value: cache_builder.build_load(global_ptr, ""), + }, + Mutability::Var => GlobalCache::Mut { + ptr_to_value: global_ptr, + }, } }) } diff --git a/lib/compiler-singlepass/src/codegen_x64.rs b/lib/compiler-singlepass/src/codegen_x64.rs index 72a5f98e5..e7378c833 100644 --- a/lib/compiler-singlepass/src/codegen_x64.rs +++ b/lib/compiler-singlepass/src/codegen_x64.rs @@ -8428,8 +8428,7 @@ pub fn gen_import_call_trampoline( ); a.emit_host_redirection(GPR::RAX); - let mut section_body = SectionBody::default(); - section_body.extend(&a.finalize().unwrap()); + let section_body = SectionBody::new_with_vec(a.finalize().unwrap().to_vec()); CustomSection { protection: CustomSectionProtection::ReadExecute, diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 6ba524c37..cfc84cbef 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -118,7 +118,7 @@ impl Compiler for SinglepassCompiler { Ok(Compilation::new(functions, import_trampolines)) } - fn compile_wasm_trampolines( + fn compile_function_call_trampolines( &self, signatures: &[FunctionType], ) -> Result, CompileError> { @@ -128,6 +128,14 @@ impl Compiler for SinglepassCompiler { .map(gen_std_trampoline) .collect()) } + + fn compile_dynamic_function_trampolines( + &self, + module: &Module, + ) -> Result, CompileError> { + Ok(PrimaryMap::new()) + // unimplemented!("Dynamic funciton trampolines not yet implemented"); + } } trait ToCompileError { diff --git a/lib/compiler/src/compiler.rs b/lib/compiler/src/compiler.rs index cf3774241..f99422635 100644 --- a/lib/compiler/src/compiler.rs +++ b/lib/compiler/src/compiler.rs @@ -9,7 +9,9 @@ use crate::target::Target; use crate::FunctionBodyData; use crate::ModuleTranslationState; use wasm_common::entity::PrimaryMap; -use wasm_common::{Features, FunctionType, LocalFunctionIndex, MemoryIndex, TableIndex}; +use wasm_common::{ + Features, FunctionIndex, FunctionType, LocalFunctionIndex, MemoryIndex, TableIndex, +}; use wasmer_runtime::Module; use wasmer_runtime::{MemoryPlan, TablePlan}; use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig}; @@ -80,8 +82,30 @@ pub trait Compiler { /// let func = instance.exports.func("my_func"); /// func.call(&[Value::I32(1)]); /// ``` - fn compile_wasm_trampolines( + fn compile_function_call_trampolines( &self, signatures: &[FunctionType], ) -> Result, CompileError>; + + /// Compile the trampolines to call a dynamic function defined in + /// a host, from a Wasm module. + /// + /// This allows us to create dynamic Wasm functions, such as: + /// + /// ```ignore + /// fn my_func(values: Vec) -> Vec { + /// // do something + /// } + /// + /// let my_func_type = FuncType::new(vec![Type::I32], vec![Type::I32]); + /// let imports = imports!{ + /// "namespace" => { + /// "my_func" => Func::new_dynamic(my_func_type, my_func),s + /// } + /// } + /// ``` + fn compile_dynamic_function_trampolines( + &self, + module: &Module, + ) -> Result, CompileError>; } diff --git a/lib/compiler/src/lib.rs b/lib/compiler/src/lib.rs index 19edf11b0..0cbd228ef 100644 --- a/lib/compiler/src/lib.rs +++ b/lib/compiler/src/lib.rs @@ -78,6 +78,7 @@ pub use crate::unwind::{CompiledFunctionUnwindInfo, FDERelocEntry, FunctionTable pub use wasm_common::Features; +#[cfg(feature = "translator")] /// wasmparser is exported as a module to slim compiler dependencies pub mod wasmparser { pub use wasmparser::*; diff --git a/lib/compiler/src/section.rs b/lib/compiler/src/section.rs index aab12cd4c..454c4be79 100644 --- a/lib/compiler/src/section.rs +++ b/lib/compiler/src/section.rs @@ -25,7 +25,6 @@ pub enum CustomSectionProtection { // We don't include `ReadWrite` here because it would complicate freeze // and resumption of executing Modules. - /// A custom section with read and execute permissions. ReadExecute, } @@ -56,14 +55,9 @@ pub struct CustomSection { pub struct SectionBody(#[serde(with = "serde_bytes")] Vec); impl SectionBody { - /// Extend the section with the bytes given. - pub fn extend(&mut self, contents: &[u8]) { - self.0.extend(contents); - } - - /// Extends the section by appending bytes from another section. - pub fn append(&mut self, body: &Self) { - self.0.extend(&body.0); + /// Create a new section body with the given contents. + pub fn new_with_vec(contents: Vec) -> Self { + Self(contents) } /// Returns a raw pointer to the section's buffer. @@ -83,6 +77,6 @@ impl SectionBody { /// Returns whether or not the section body is empty. pub fn is_empty(&self) -> bool { - self.len() == 0 + self.0.is_empty() } } diff --git a/lib/compiler/src/target.rs b/lib/compiler/src/target.rs index 4bda783a1..ed230edf1 100644 --- a/lib/compiler/src/target.rs +++ b/lib/compiler/src/target.rs @@ -5,20 +5,19 @@ pub use target_lexicon::{Architecture, CallingConvention, OperatingSystem, Tripl #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use raw_cpuid::CpuId; -/// The nomenclature is inspired by the [raw-cpuid crate]. +/// The nomenclature is inspired by the [`cpuid` crate]. /// The list of supported features was initially retrieved from -/// [cranelift-native]. +/// [`cranelift-native`]. /// -/// The `CpuFeature` enum vaues are likely to grow closer to the -/// original cpuid. However, we prefer to start small and grow from there. +/// The `CpuFeature` enum values are likely to grow closer to the +/// original `cpuid`. However, we prefer to start small and grow from there. /// /// If you would like to use a flag that doesn't exist yet here, please /// open a PR. /// -/// [cpuid crate]: https://docs.rs/cpuid/0.1.1/cpuid/enum.CpuFeature.html -/// [cranelift-native]: https://github.com/bytecodealliance/cranelift/blob/6988545fd20249b084c53f4761b8c861266f5d31/cranelift-native/src/lib.rs#L51-L92 -#[allow(missing_docs)] -#[allow(clippy::derive_hash_xor_eq)] +/// [`cpuid` crate]: https://docs.rs/cpuid/0.1.1/cpuid/enum.CpuFeature.html +/// [`cranelift-native`]: https://github.com/bytecodealliance/cranelift/blob/6988545fd20249b084c53f4761b8c861266f5d31/cranelift-native/src/lib.rs#L51-L92 +#[allow(missing_docs, clippy::derive_hash_xor_eq)] #[derive(EnumSetType, Debug, Hash)] pub enum CpuFeature { // X86 features diff --git a/lib/engine-jit/src/engine.rs b/lib/engine-jit/src/engine.rs index 685defff2..33f71ed7b 100644 --- a/lib/engine-jit/src/engine.rs +++ b/lib/engine-jit/src/engine.rs @@ -4,10 +4,9 @@ use crate::{CodeMemory, CompiledModule}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use wasm_common::entity::PrimaryMap; -use wasm_common::{FunctionType, LocalFunctionIndex, MemoryIndex, SignatureIndex, TableIndex}; +use wasm_common::{FunctionIndex, FunctionType, LocalFunctionIndex, SignatureIndex}; use wasmer_compiler::{ - Compilation, CompileError, CustomSection, CustomSectionProtection, FunctionBody, SectionIndex, - Target, + CompileError, CustomSection, CustomSectionProtection, FunctionBody, SectionIndex, Target, }; #[cfg(feature = "compiler")] use wasmer_compiler::{Compiler, CompilerConfig}; @@ -42,7 +41,7 @@ impl JITEngine { Self { inner: Arc::new(Mutex::new(JITEngineInner { compiler: Some(compiler), - trampolines: HashMap::new(), + function_call_trampolines: HashMap::new(), code_memory: CodeMemory::new(), signatures: SignatureRegistry::new(), })), @@ -68,7 +67,7 @@ impl JITEngine { inner: Arc::new(Mutex::new(JITEngineInner { #[cfg(feature = "compiler")] compiler: None, - trampolines: HashMap::new(), + function_call_trampolines: HashMap::new(), code_memory: CodeMemory::new(), signatures: SignatureRegistry::new(), })), @@ -110,8 +109,8 @@ impl Engine for JITEngine { } /// Retrieves a trampoline given a signature - fn trampoline(&self, sig: VMSharedSignatureIndex) -> Option { - self.compiler().trampoline(sig) + fn function_call_trampoline(&self, sig: VMSharedSignatureIndex) -> Option { + self.compiler().function_call_trampoline(sig) } /// Validates a WebAssembly module @@ -176,7 +175,7 @@ pub struct JITEngineInner { #[cfg(feature = "compiler")] compiler: Option>, /// Pointers to trampoline functions used to enter particular signatures - trampolines: HashMap, + function_call_trampolines: HashMap, /// The code memory is responsible of publishing the compiled /// functions to memory. code_memory: CodeMemory, @@ -237,8 +236,15 @@ impl JITEngineInner { &mut self, module: &Module, functions: &PrimaryMap, - trampolines: &PrimaryMap, - ) -> Result, CompileError> { + function_call_trampolines: &PrimaryMap, + dynamic_function_trampolines: &PrimaryMap, + ) -> Result< + ( + PrimaryMap, + PrimaryMap, + ), + CompileError, + > { // Allocate all of the compiled functions into executable memory, // copying over their contents. let allocated_functions = @@ -251,10 +257,10 @@ impl JITEngineInner { )) })?; - for (sig_index, compiled_function) in trampolines.iter() { + for (sig_index, compiled_function) in function_call_trampolines.iter() { let func_type = module.signatures.get(sig_index).unwrap(); let index = self.signatures.register(&func_type); - if self.trampolines.contains_key(&index) { + if self.function_call_trampolines.contains_key(&index) { // We don't need to allocate the trampoline in case // it's signature is already allocated. continue; @@ -264,16 +270,34 @@ impl JITEngineInner { .allocate_for_function(&compiled_function) .map_err(|message| { CompileError::Resource(format!( - "failed to allocate memory for trampolines: {}", + "failed to allocate memory for function call trampolines: {}", message )) })? .as_ptr(); let trampoline = unsafe { std::mem::transmute::<*const VMFunctionBody, VMTrampoline>(ptr) }; - self.trampolines.insert(index, trampoline); + self.function_call_trampolines.insert(index, trampoline); } - Ok(allocated_functions) + + let allocated_dynamic_function_trampolines = dynamic_function_trampolines + .values() + .map(|compiled_function| { + let ptr = self + .code_memory + .allocate_for_function(&compiled_function) + .map_err(|message| { + CompileError::Resource(format!( + "failed to allocate memory for dynamic function trampolines: {}", + message + )) + })? + .as_ptr(); + Ok(ptr) + }) + .collect::, CompileError>>()?; + + Ok((allocated_functions, allocated_dynamic_function_trampolines)) } /// Make memory containing compiled code executable. @@ -287,7 +311,7 @@ impl JITEngineInner { } /// Gets the trampoline pre-registered for a particular signature - pub fn trampoline(&self, sig: VMSharedSignatureIndex) -> Option { - self.trampolines.get(&sig).cloned() + pub fn function_call_trampoline(&self, sig: VMSharedSignatureIndex) -> Option { + self.function_call_trampolines.get(&sig).cloned() } } diff --git a/lib/engine-jit/src/module.rs b/lib/engine-jit/src/module.rs index 1e8d96f75..7ce7035c9 100644 --- a/lib/engine-jit/src/module.rs +++ b/lib/engine-jit/src/module.rs @@ -8,8 +8,8 @@ use std::any::Any; use std::sync::{Arc, Mutex}; use wasm_common::entity::{BoxedSlice, PrimaryMap}; use wasm_common::{ - DataInitializer, LocalFunctionIndex, MemoryIndex, OwnedDataInitializer, SignatureIndex, - TableIndex, + DataInitializer, FunctionIndex, LocalFunctionIndex, MemoryIndex, OwnedDataInitializer, + SignatureIndex, TableIndex, }; use wasmer_compiler::CompileError; #[cfg(feature = "compiler")] @@ -30,6 +30,7 @@ pub struct CompiledModule { serializable: SerializableModule, finished_functions: BoxedSlice, + finished_dynamic_function_trampolines: BoxedSlice, signatures: BoxedSlice, frame_info_registration: Mutex>>, } @@ -75,11 +76,14 @@ impl CompiledModule { .values() .cloned() .collect::>(); - let trampolines = compiler - .compile_wasm_trampolines(&func_types)? + let function_call_trampolines = compiler + .compile_function_call_trampolines(&func_types)? .into_iter() .collect::>(); + let dynamic_function_trampolines = + compiler.compile_dynamic_function_trampolines(&translation.module)?; + let data_initializers = translation .data_initializers .iter() @@ -98,7 +102,8 @@ impl CompiledModule { function_relocations: compilation.get_relocations(), function_jt_offsets: compilation.get_jt_offsets(), function_frame_info: frame_infos, - trampolines, + function_call_trampolines, + dynamic_function_trampolines, custom_sections: compilation.get_custom_sections(), custom_section_relocations: compilation.get_custom_section_relocations(), }; @@ -146,10 +151,11 @@ impl CompiledModule { jit_compiler: &mut JITEngineInner, serializable: SerializableModule, ) -> Result { - let finished_functions = jit_compiler.allocate( + let (finished_functions, finished_dynamic_function_trampolines) = jit_compiler.allocate( &serializable.module, &serializable.compilation.function_bodies, - &serializable.compilation.trampolines, + &serializable.compilation.function_call_trampolines, + &serializable.compilation.dynamic_function_trampolines, )?; let custom_sections = jit_compiler.allocate_custom_sections(&serializable.compilation.custom_sections)?; @@ -180,6 +186,8 @@ impl CompiledModule { Ok(Self { serializable, finished_functions: finished_functions.into_boxed_slice(), + finished_dynamic_function_trampolines: finished_dynamic_function_trampolines + .into_boxed_slice(), signatures: signatures.into_boxed_slice(), frame_info_registration: Mutex::new(None), }) @@ -211,6 +219,7 @@ impl CompiledModule { &self.module(), &sig_registry, resolver, + &self.finished_dynamic_function_trampolines, self.memory_plans(), self.table_plans(), ) diff --git a/lib/engine-jit/src/serialize.rs b/lib/engine-jit/src/serialize.rs index f7e51a277..0f20833eb 100644 --- a/lib/engine-jit/src/serialize.rs +++ b/lib/engine-jit/src/serialize.rs @@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use wasm_common::entity::PrimaryMap; use wasm_common::{ - Features, LocalFunctionIndex, MemoryIndex, OwnedDataInitializer, SignatureIndex, TableIndex, + Features, FunctionIndex, LocalFunctionIndex, MemoryIndex, OwnedDataInitializer, SignatureIndex, + TableIndex, }; use wasmer_compiler::{ CustomSection, FunctionBody, JumpTableOffsets, Relocation, SectionBody, SectionIndex, @@ -21,7 +22,8 @@ pub struct SerializableCompilation { // to allow lazy frame_info deserialization, we convert it to it's lazy binary // format upon serialization. pub function_frame_info: PrimaryMap, - pub trampolines: PrimaryMap, + pub function_call_trampolines: PrimaryMap, + pub dynamic_function_trampolines: PrimaryMap, pub custom_sections: PrimaryMap, pub custom_section_relocations: PrimaryMap>, } diff --git a/lib/engine/src/engine.rs b/lib/engine/src/engine.rs index 53b15eb73..06cac18a1 100644 --- a/lib/engine/src/engine.rs +++ b/lib/engine/src/engine.rs @@ -24,7 +24,7 @@ pub trait Engine { fn lookup_signature(&self, sig: VMSharedSignatureIndex) -> Option; /// Retrieves a trampoline given a signature - fn trampoline(&self, sig: VMSharedSignatureIndex) -> Option; + fn function_call_trampoline(&self, sig: VMSharedSignatureIndex) -> Option; /// Validates a WebAssembly module fn validate(&self, binary: &[u8]) -> Result<(), CompileError>; diff --git a/lib/engine/src/resolver.rs b/lib/engine/src/resolver.rs index 58c1c0d63..c3fa83592 100644 --- a/lib/engine/src/resolver.rs +++ b/lib/engine/src/resolver.rs @@ -3,11 +3,11 @@ use crate::error::{ImportError, LinkError}; use more_asserts::assert_ge; -use wasm_common::entity::PrimaryMap; -use wasm_common::{ExternType, ImportIndex, MemoryIndex, TableIndex}; +use wasm_common::entity::{BoxedSlice, EntityRef, PrimaryMap}; +use wasm_common::{ExternType, FunctionIndex, ImportIndex, MemoryIndex, TableIndex}; use wasmer_runtime::{ - Export, Imports, SignatureRegistry, VMFunctionImport, VMGlobalImport, VMMemoryImport, - VMTableImport, + Export, Imports, SignatureRegistry, VMFunctionBody, VMFunctionImport, VMFunctionKind, + VMGlobalImport, VMMemoryImport, VMTableImport, }; use wasmer_runtime::{MemoryPlan, TablePlan}; @@ -91,6 +91,7 @@ pub fn resolve_imports( module: &Module, signatures: &SignatureRegistry, resolver: &dyn Resolver, + finished_dynamic_function_trampolines: &BoxedSlice, memory_plans: &PrimaryMap, _table_plans: &PrimaryMap, ) -> Result { @@ -122,8 +123,21 @@ pub fn resolve_imports( } match resolved { Export::Function(ref f) => { + let address = match f.kind { + VMFunctionKind::Dynamic => { + // If this is a dynamic imported function, + // the address of the funciton is the address of the + // reverse trampoline. + let index = FunctionIndex::new(function_imports.len()); + finished_dynamic_function_trampolines[index] + + // TODO: We should check that the f.vmctx actually matches + // the shape of `VMDynamicFunctionImportContext` + } + VMFunctionKind::Static => f.address, + }; function_imports.push(VMFunctionImport { - body: f.address, + body: address, vmctx: f.vmctx, }); } diff --git a/lib/runtime/src/export.rs b/lib/runtime/src/export.rs index e45ea251c..52ed8df59 100644 --- a/lib/runtime/src/export.rs +++ b/lib/runtime/src/export.rs @@ -2,8 +2,8 @@ use crate::memory::LinearMemory; use crate::module::{MemoryPlan, TablePlan}; use crate::table::Table; use crate::vmcontext::{ - VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMSharedSignatureIndex, - VMTableDefinition, + VMContext, VMFunctionBody, VMFunctionKind, VMGlobalDefinition, VMMemoryDefinition, + VMSharedSignatureIndex, VMTableDefinition, }; use wasm_common::GlobalType; @@ -34,6 +34,8 @@ pub struct ExportFunction { /// /// Note that this indexes within the module associated with `vmctx`. pub signature: VMSharedSignatureIndex, + /// The function kind (it defines how it's the signature that provided `address` have) + pub kind: VMFunctionKind, } impl From for Export { diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs index 5dce2f67c..a7444256c 100644 --- a/lib/runtime/src/instance.rs +++ b/lib/runtime/src/instance.rs @@ -8,8 +8,8 @@ use crate::table::Table; use crate::trap::{catch_traps, init_traps, Trap, TrapCode}; use crate::vmcontext::{ VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, - VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, - VMTableDefinition, VMTableImport, + VMFunctionKind, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, + VMSharedSignatureIndex, VMTableDefinition, VMTableImport, }; use crate::{ExportFunction, ExportGlobal, ExportMemory, ExportTable}; use crate::{Module, TableElements, VMOffsets}; @@ -294,6 +294,11 @@ impl Instance { }; ExportFunction { address, + // Any function received is already static at this point as: + // 1. All locally defined functions in the Wasm have a static signature. + // 2. All the imported functions are already static (because + // they point to the trampolines rather than the dynamic addresses). + kind: VMFunctionKind::Static, signature, vmctx, } diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index 0a2711aa2..1ff5d9468 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -50,9 +50,10 @@ pub use crate::sig_registry::SignatureRegistry; pub use crate::table::Table; pub use crate::trap::*; pub use crate::vmcontext::{ - VMBuiltinFunctionIndex, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, - VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, - VMTableDefinition, VMTableImport, VMTrampoline, + VMBuiltinFunctionIndex, VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionImportContext, + VMFunctionBody, VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport, + VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, + VMTrampoline, }; pub use crate::vmoffsets::{TargetSharedSignatureIndex, VMOffsets}; diff --git a/lib/runtime/src/vmcontext.rs b/lib/runtime/src/vmcontext.rs index 758a463fb..abdc8c8cc 100644 --- a/lib/runtime/src/vmcontext.rs +++ b/lib/runtime/src/vmcontext.rs @@ -46,6 +46,52 @@ mod test_vmfunction_import { } } +/// The `VMDynamicFunctionImportContext` is the context that dynamic +/// functions will receive when called (rather than `vmctx`). +/// A dynamic function is a function for which we don't know the signature +/// until runtime. +/// +/// As such, we need to expose the dynamic function `context` +/// containing the relevant context for running the function indicated +/// in `address`. +#[repr(C)] +pub struct VMDynamicFunctionImportContext { + /// The address of the inner dynamic function. + /// + /// Note: The function must be on the form of + /// `(*mut T, *mut VMContext, SignatureIndex, *mut i128)`. + pub address: *const VMFunctionBody, + + /// The context that the inner dynamic function will receive. + pub ctx: T, +} + +#[cfg(test)] +mod test_vmdynamicfunction_import_context { + use super::VMDynamicFunctionImportContext; + use crate::{Module, VMOffsets}; + use memoffset::offset_of; + use std::mem::size_of; + + #[test] + fn check_vmdynamicfunction_import_context_offsets() { + let module = Module::new(); + let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module); + assert_eq!( + size_of::>(), + usize::from(offsets.size_of_vmdynamicfunction_import_context()) + ); + assert_eq!( + offset_of!(VMDynamicFunctionImportContext, address), + usize::from(offsets.vmdynamicfunction_import_context_address()) + ); + assert_eq!( + offset_of!(VMDynamicFunctionImportContext, ctx), + usize::from(offsets.vmdynamicfunction_import_context_ctx()) + ); + } +} + /// A placeholder byte-sized type which is just used to provide some amount of type /// safety when dealing with pointers to JIT-compiled function bodies. Note that it's /// deliberately not Copy, as we shouldn't be carelessly copying function body bytes @@ -64,6 +110,26 @@ mod test_vmfunction_body { } } +/// A function kind. +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub enum VMFunctionKind { + /// A function is static when it's address matches the signature: + /// (vmctx, vmctx, arg1, arg2...) -> (result1, result2, ...) + /// + /// This is the default for functions that are defined: + /// 1. In the Host, natively + /// 2. In the WebAssembly file + Static, + + /// A function is dynamic when it's address matches the signature: + /// (ctx, &[Type]) -> Vec + /// + /// This is the default for functions that are defined: + /// 1. In the Host, dynamically + Dynamic, +} + /// The fields compiled code needs to access to utilize a WebAssembly table /// imported from another instance. #[derive(Debug, Copy, Clone)] diff --git a/lib/runtime/src/vmoffsets.rs b/lib/runtime/src/vmoffsets.rs index ef4898094..9afae8d03 100644 --- a/lib/runtime/src/vmoffsets.rs +++ b/lib/runtime/src/vmoffsets.rs @@ -92,6 +92,30 @@ impl VMOffsets { } } +/// Offsets for [`VMDynamicFunctionImportContext`]. +/// +/// [`VMDynamicFunctionImportContext`]: crate::vmcontext::VMDynamicFunctionImportContext +impl VMOffsets { + /// The offset of the `address` field. + #[allow(clippy::erasing_op)] + pub fn vmdynamicfunction_import_context_address(&self) -> u8 { + 0 * self.pointer_size + } + + /// The offset of the `ctx` field. + #[allow(clippy::identity_op)] + pub fn vmdynamicfunction_import_context_ctx(&self) -> u8 { + 1 * self.pointer_size + } + + /// Return the size of [`VMDynamicFunctionImportContext`]. + /// + /// [`VMDynamicFunctionImportContext`]: crate::vmcontext::VMDynamicFunctionImportContext + pub fn size_of_vmdynamicfunction_import_context(&self) -> u8 { + 2 * self.pointer_size + } +} + /// Offsets for `*const VMFunctionBody`. impl VMOffsets { /// The size of the `current_elements` field. diff --git a/lib/wasm-common/src/indexes.rs b/lib/wasm-common/src/indexes.rs index 69a15e831..e761f9d72 100644 --- a/lib/wasm-common/src/indexes.rs +++ b/lib/wasm-common/src/indexes.rs @@ -55,6 +55,7 @@ entity_impl!(MemoryIndex); /// Index type of a signature (imported or local) inside the WebAssembly module. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +#[repr(transparent)] pub struct SignatureIndex(u32); entity_impl!(SignatureIndex); diff --git a/lib/wasm-common/src/native.rs b/lib/wasm-common/src/native.rs index d718a8394..d45739f3d 100644 --- a/lib/wasm-common/src/native.rs +++ b/lib/wasm-common/src/native.rs @@ -261,39 +261,26 @@ pub struct FunctionBody(*mut u8); /// Represents a function that can be used by WebAssembly. #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct Func { +pub struct Func { address: *const FunctionBody, - env: Option<*mut Env>, _phantom: PhantomData<(Args, Rets)>, } unsafe impl Send for Func {} -impl Func +impl Func where Args: WasmTypeList, Rets: WasmTypeList, - Env: Sized, { /// Creates a new `Func`. - pub fn new(func: F) -> Self + pub fn new(func: F) -> Self where - F: HostFunction, + F: HostFunction, + T: HostFunctionKind, + E: Sized, { Self { - env: None, - address: func.to_raw(), - _phantom: PhantomData, - } - } - - /// Creates a new `Func` with a given `env`. - pub fn new_env(env: &mut Env, func: F) -> Self - where - F: HostFunction, - { - Self { - env: Some(env), address: func.to_raw(), _phantom: PhantomData, } @@ -304,11 +291,6 @@ where FunctionType::new(Args::wasm_types(), Rets::wasm_types()) } - /// Get the type of the Func - pub fn env(&self) -> Option<*mut Env> { - self.env - } - /// Get the address of the Func pub fn address(&self) -> *const FunctionBody { self.address