diff --git a/crates/cranelift/Cargo.toml b/crates/cranelift/Cargo.toml index 66e8de85ba..5807b4a6c8 100644 --- a/crates/cranelift/Cargo.toml +++ b/crates/cranelift/Cargo.toml @@ -28,3 +28,4 @@ thiserror = "1.0.4" [features] all-arch = ["cranelift-codegen/all-arch"] +component-model = ["wasmtime-environ/component-model"] diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 1afbc25e89..6dcdeeac1a 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -34,6 +34,9 @@ use wasmtime_environ::{ TrapCode, TrapEncodingBuilder, TrapInformation, Tunables, VMOffsets, }; +#[cfg(feature = "component-model")] +mod component; + struct CompilerContext { func_translator: FuncTranslator, codegen_context: Context, @@ -372,6 +375,11 @@ impl wasmtime_environ::Compiler for Compiler { .map(|val| (val.name.to_string(), to_flag_value(val))) .collect() } + + #[cfg(feature = "component-model")] + fn component_compiler(&self) -> &dyn wasmtime_environ::component::ComponentCompiler { + self + } } fn to_flag_value(v: &settings::Value) -> FlagValue { diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs new file mode 100644 index 0000000000..ffd3ed4abe --- /dev/null +++ b/crates/cranelift/src/compiler/component.rs @@ -0,0 +1,170 @@ +//! Compilation support for the component model. + +use crate::compiler::{Compiler, CompilerContext}; +use crate::obj::ModuleTextBuilder; +use crate::CompiledFunction; +use anyhow::Result; +use cranelift_codegen::ir::{self, InstBuilder, MemFlags}; +use cranelift_frontend::FunctionBuilder; +use object::write::Object; +use std::any::Any; +use wasmtime_environ::component::{ + CanonicalOptions, Component, ComponentCompiler, ComponentTypes, LowerImport, LoweredIndex, + TrampolineInfo, VMComponentOffsets, +}; +use wasmtime_environ::PrimaryMap; + +impl ComponentCompiler for Compiler { + fn compile_lowered_trampoline( + &self, + component: &Component, + lowering: &LowerImport, + types: &ComponentTypes, + ) -> Result> { + let ty = &types[lowering.canonical_abi]; + let isa = &*self.isa; + let pointer_type = isa.pointer_type(); + let offsets = VMComponentOffsets::new(isa.pointer_bytes(), component); + + let CompilerContext { + mut func_translator, + codegen_context: mut context, + } = self.take_context(); + + context.func = ir::Function::with_name_signature( + ir::ExternalName::user(0, 0), + crate::indirect_signature(isa, ty), + ); + + let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context()); + let block0 = builder.create_block(); + + // Start off by spilling all the wasm arguments into a stack slot to be + // passed to the host function. + let (values_vec_ptr_val, values_vec_len) = + self.wasm_to_host_spill_args(ty, &mut builder, block0); + let vmctx = builder.func.dfg.block_params(block0)[0]; + + // Below this will incrementally build both the signature of the host + // function we're calling as well as the list of arguments since the + // list is somewhat long. + let mut callee_args = Vec::new(); + let mut host_sig = ir::Signature::new(crate::wasmtime_call_conv(isa)); + + let CanonicalOptions { + memory, + realloc, + string_encoding, + } = lowering.options; + + // vmctx: *mut VMComponentContext + host_sig.params.push(ir::AbiParam::new(pointer_type)); + callee_args.push(vmctx); + + // data: *mut u8, + host_sig.params.push(ir::AbiParam::new(pointer_type)); + callee_args.push(builder.ins().load( + pointer_type, + MemFlags::trusted(), + vmctx, + i32::try_from(offsets.lowering_data(lowering.index)).unwrap(), + )); + + // memory: *mut VMMemoryDefinition + host_sig.params.push(ir::AbiParam::new(pointer_type)); + callee_args.push(match memory { + Some(idx) => builder.ins().load( + pointer_type, + MemFlags::trusted(), + vmctx, + i32::try_from(offsets.runtime_memory(idx)).unwrap(), + ), + None => builder.ins().iconst(pointer_type, 0), + }); + + // realloc: *mut VMCallerCheckedAnyfunc + host_sig.params.push(ir::AbiParam::new(pointer_type)); + callee_args.push(match realloc { + Some(idx) => builder.ins().load( + pointer_type, + MemFlags::trusted(), + vmctx, + i32::try_from(offsets.runtime_realloc(idx)).unwrap(), + ), + None => builder.ins().iconst(pointer_type, 0), + }); + + // string_encoding: StringEncoding + host_sig.params.push(ir::AbiParam::new(ir::types::I8)); + callee_args.push( + builder + .ins() + .iconst(ir::types::I8, i64::from(string_encoding as u8)), + ); + + // storage: *mut ValRaw + host_sig.params.push(ir::AbiParam::new(pointer_type)); + callee_args.push(values_vec_ptr_val); + + // storage_len: usize + host_sig.params.push(ir::AbiParam::new(pointer_type)); + callee_args.push( + builder + .ins() + .iconst(pointer_type, i64::from(values_vec_len)), + ); + + // Load host function pointer from the vmcontext and then call that + // indirect function pointer with the list of arguments. + let host_fn = builder.ins().load( + pointer_type, + MemFlags::trusted(), + vmctx, + i32::try_from(offsets.lowering_callee(lowering.index)).unwrap(), + ); + let host_sig = builder.import_signature(host_sig); + builder.ins().call_indirect(host_sig, host_fn, &callee_args); + + // After the host function has returned the results are loaded from + // `values_vec_ptr_val` and then returned. + self.wasm_to_host_load_results(ty, &mut builder, values_vec_ptr_val); + + let func: CompiledFunction = self.finish_trampoline(&mut context, isa)?; + self.save_context(CompilerContext { + func_translator, + codegen_context: context, + }); + Ok(Box::new(func)) + } + + fn emit_obj( + &self, + trampolines: PrimaryMap>, + obj: &mut Object<'static>, + ) -> Result> { + let trampolines: PrimaryMap = trampolines + .into_iter() + .map(|(_, f)| *f.downcast().unwrap()) + .collect(); + let module = Default::default(); + let mut text = ModuleTextBuilder::new(obj, &module, &*self.isa); + let mut ret = PrimaryMap::new(); + for (idx, trampoline) in trampolines.iter() { + let (_symbol, range) = text.append_func( + false, + format!("_wasm_component_host_trampoline{}", idx.as_u32()).into_bytes(), + &trampoline, + ); + + let i = ret.push(TrampolineInfo { + start: u32::try_from(range.start).unwrap(), + length: u32::try_from(range.end - range.start).unwrap(), + }); + assert_eq!(i, idx); + } + + text.finish()?; + + Ok(ret) + } +} diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 90ff76c7a6..8c64cd62a9 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -16,7 +16,7 @@ use std::convert::TryFrom; use std::mem; use wasmparser::Operator; use wasmtime_environ::{ - BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleTypes, + BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleTypes, PtrSize, TableStyle, Tunables, VMOffsets, WASM_PAGE_SIZE, }; use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK}; @@ -1536,7 +1536,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m pointer_type, mem_flags, anyfunc_ptr, - i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()), + i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_func_ptr()), ); // If necessary, check the signature. @@ -1572,7 +1572,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m sig_id_type, mem_flags, anyfunc_ptr, - i32::from(self.offsets.vmcaller_checked_anyfunc_type_index()), + i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_type_index()), ); // Check that they match. @@ -1594,7 +1594,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m pointer_type, mem_flags, anyfunc_ptr, - i32::from(self.offsets.vmcaller_checked_anyfunc_vmctx()), + i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_vmctx()), ); real_call_args.push(vmctx); real_call_args.push(caller_vmctx); diff --git a/crates/cranelift/src/obj.rs b/crates/cranelift/src/obj.rs index 063afd2393..213123c7bb 100644 --- a/crates/cranelift/src/obj.rs +++ b/crates/cranelift/src/obj.rs @@ -88,7 +88,7 @@ impl<'a> ModuleTextBuilder<'a> { /// /// Returns the symbol associated with the function as well as the range /// that the function resides within the text section. - fn append_func( + pub fn append_func( &mut self, labeled: bool, name: Vec, diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index bd43d15d34..a50257a566 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -232,6 +232,13 @@ pub trait Compiler: Send + Sync { /// Same as [`Compiler::flags`], but ISA-specific (a cranelift-ism) fn isa_flags(&self) -> BTreeMap; + + /// Returns a suitable compiler usable for component-related compliations. + /// + /// Note that the `ComponentCompiler` trait can also be implemented for + /// `Self` in which case this function would simply return `self`. + #[cfg(feature = "component-model")] + fn component_compiler(&self) -> &dyn crate::component::ComponentCompiler; } /// Value of a configured setting for a [`Compiler`] diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs index 3295d34975..20bb2ec993 100644 --- a/crates/environ/src/component.rs +++ b/crates/environ/src/component.rs @@ -26,9 +26,13 @@ //! any time. Some comments may reflect historical rather than current state as //! well (sorry). +mod compiler; mod info; mod translate; mod types; +mod vmcomponent_offsets; +pub use self::compiler::*; pub use self::info::*; pub use self::translate::*; pub use self::types::*; +pub use self::vmcomponent_offsets::*; diff --git a/crates/environ/src/component/compiler.rs b/crates/environ/src/component/compiler.rs new file mode 100644 index 0000000000..aa9f566544 --- /dev/null +++ b/crates/environ/src/component/compiler.rs @@ -0,0 +1,58 @@ +use crate::component::{Component, ComponentTypes, LowerImport, LoweredIndex}; +use crate::PrimaryMap; +use anyhow::Result; +use object::write::Object; +use serde::{Deserialize, Serialize}; +use std::any::Any; + +/// Description of where a trampoline is located in the text section of a +/// compiled image. +#[derive(Serialize, Deserialize)] +pub struct TrampolineInfo { + /// The byte offset from the start of the text section where this trampoline + /// starts. + pub start: u32, + /// The byte length of this trampoline's function body. + pub length: u32, +} + +/// Compilation support necessary for components. +pub trait ComponentCompiler: Send + Sync { + /// Creates a trampoline for a `canon.lower`'d host function. + /// + /// This function will create a suitable trampoline which can be called from + /// WebAssembly code and which will then call into host code. The signature + /// of this generated trampoline should have the appropriate wasm ABI for + /// the `lowering.canonical_abi` type signature (e.g. System-V). + /// + /// The generated trampoline will interpret its first argument as a + /// `*mut VMComponentContext` and use the `VMComponentOffsets` for + /// `component` to read necessary data (as specified by `lowering.options`) + /// and call the host function pointer. Notably the host function pointer + /// has the signature `VMLoweringCallee` where many of the arguments are + /// loaded from known offsets (for this particular generated trampoline) + /// from the `VMComponentContext`. + /// + /// Returns a compiler-specific `Box` which can be passed later to + /// `emit_obj` to crate an elf object. + fn compile_lowered_trampoline( + &self, + component: &Component, + lowering: &LowerImport, + types: &ComponentTypes, + ) -> Result>; + + /// Emits the `trampolines` specified into the in-progress ELF object + /// specified by `obj`. + /// + /// Returns a map of trampoline information for where to find them all in + /// the text section. + /// + /// Note that this will also prepare unwinding information for all the + /// trampolines as necessary. + fn emit_obj( + &self, + trampolines: PrimaryMap>, + obj: &mut Object<'static>, + ) -> Result>; +} diff --git a/crates/environ/src/component/vmcomponent_offsets.rs b/crates/environ/src/component/vmcomponent_offsets.rs new file mode 100644 index 0000000000..02be59d3c6 --- /dev/null +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -0,0 +1,240 @@ +// Currently the `VMComponentContext` allocation by field looks like this: +// +// struct VMComponentContext { +// magic: u32, +// may_enter: u8, +// may_leave: u8, +// store: *mut dyn Store, +// lowering_anyfuncs: [VMCallerCheckedAnyfunc; component.num_lowerings], +// lowerings: [VMLowering; component.num_lowerings], +// memories: [*mut VMMemoryDefinition; component.num_memories], +// reallocs: [*mut VMCallerCheckedAnyfunc; component.num_reallocs], +// } + +use crate::component::{Component, LoweredIndex, RuntimeMemoryIndex, RuntimeReallocIndex}; +use crate::PtrSize; + +/// Equivalent of `VMCONTEXT_MAGIC` except for components. +/// +/// This is stored at the start of all `VMComponentContext` structures adn +/// double-checked on `VMComponentContext::from_opaque`. +pub const VMCOMPONENT_MAGIC: u32 = u32::from_le_bytes(*b"comp"); + +/// Runtime offsets within a `VMComponentContext` for a specific component. +#[derive(Debug, Clone, Copy)] +pub struct VMComponentOffsets

{ + /// The host pointer size + pub ptr: P, + + /// The number of lowered functions this component will be creating. + pub num_lowerings: u32, + /// The number of memories which are recorded in this component for options. + pub num_runtime_memories: u32, + /// The number of reallocs which are recorded in this component for options. + pub num_runtime_reallocs: u32, + + // precalculated offsets of various member fields + magic: u32, + may_enter: u32, + may_leave: u32, + store: u32, + lowering_anyfuncs: u32, + lowerings: u32, + memories: u32, + reallocs: u32, + size: u32, +} + +#[inline] +fn align(offset: u32, align: u32) -> u32 { + assert!(align.is_power_of_two()); + (offset + (align - 1)) & !(align - 1) +} + +impl VMComponentOffsets

{ + /// Creates a new set of offsets for the `component` specified configured + /// additionally for the `ptr` size specified. + pub fn new(ptr: P, component: &Component) -> Self { + let mut ret = Self { + ptr, + num_lowerings: component.num_lowerings.try_into().unwrap(), + num_runtime_memories: component.num_runtime_memories.try_into().unwrap(), + num_runtime_reallocs: component.num_runtime_reallocs.try_into().unwrap(), + magic: 0, + may_enter: 0, + may_leave: 0, + store: 0, + lowering_anyfuncs: 0, + lowerings: 0, + memories: 0, + reallocs: 0, + size: 0, + }; + + // Convenience functions for checked addition and multiplication. + // As side effect this reduces binary size by using only a single + // `#[track_caller]` location for each function instead of one for + // each individual invocation. + #[inline] + fn cmul(count: u32, size: u8) -> u32 { + count.checked_mul(u32::from(size)).unwrap() + } + + let mut next_field_offset = 0; + + macro_rules! fields { + (size($field:ident) = $size:expr, $($rest:tt)*) => { + ret.$field = next_field_offset; + next_field_offset = next_field_offset.checked_add(u32::from($size)).unwrap(); + fields!($($rest)*); + }; + (align($align:expr), $($rest:tt)*) => { + next_field_offset = align(next_field_offset, $align); + fields!($($rest)*); + }; + () => {}; + } + + fields! { + size(magic) = 4u32, + size(may_enter) = 1u32, + size(may_leave) = 1u32, + align(u32::from(ret.ptr.size())), + size(store) = cmul(2, ret.ptr.size()), + size(lowering_anyfuncs) = cmul(ret.num_lowerings, ret.ptr.size_of_vmcaller_checked_anyfunc()), + size(lowerings) = cmul(ret.num_lowerings, ret.ptr.size() * 2), + size(memories) = cmul(ret.num_runtime_memories, ret.ptr.size()), + size(reallocs) = cmul(ret.num_runtime_reallocs, ret.ptr.size()), + } + + ret.size = next_field_offset; + + // This is required by the implementation of + // `VMComponentContext::from_opaque`. If this value changes then this + // location needs to be updated. + assert_eq!(ret.magic, 0); + + return ret; + } + + /// The size, in bytes, of the host pointer. + #[inline] + pub fn pointer_size(&self) -> u8 { + self.ptr.size() + } + + /// The offset of the `magic` field. + #[inline] + pub fn magic(&self) -> u32 { + self.magic + } + + /// The offset of the `may_leave` field. + #[inline] + pub fn may_leave(&self) -> u32 { + self.may_leave + } + + /// The offset of the `may_enter` field. + #[inline] + pub fn may_enter(&self) -> u32 { + self.may_enter + } + + /// The offset of the `store` field. + #[inline] + pub fn store(&self) -> u32 { + self.store + } + + /// The offset of the `lowering_anyfuncs` field. + #[inline] + pub fn lowering_anyfuncs(&self) -> u32 { + self.lowering_anyfuncs + } + + /// The offset of `VMCallerCheckedAnyfunc` for the `index` specified. + #[inline] + pub fn lowering_anyfunc(&self, index: LoweredIndex) -> u32 { + assert!(index.as_u32() < self.num_lowerings); + self.lowering_anyfuncs() + + index.as_u32() * u32::from(self.ptr.size_of_vmcaller_checked_anyfunc()) + } + + /// The offset of the `lowerings` field. + #[inline] + pub fn lowerings(&self) -> u32 { + self.lowerings + } + + /// The offset of the `VMLowering` for the `index` specified. + #[inline] + pub fn lowering(&self, index: LoweredIndex) -> u32 { + assert!(index.as_u32() < self.num_lowerings); + self.lowerings() + index.as_u32() * u32::from(2 * self.ptr.size()) + } + + /// The offset of the `callee` for the `index` specified. + #[inline] + pub fn lowering_callee(&self, index: LoweredIndex) -> u32 { + self.lowering(index) + self.lowering_callee_offset() + } + + /// The offset of the `data` for the `index` specified. + #[inline] + pub fn lowering_data(&self, index: LoweredIndex) -> u32 { + self.lowering(index) + self.lowering_data_offset() + } + + /// The size of the `VMLowering` type + #[inline] + pub fn lowering_size(&self) -> u8 { + 2 * self.ptr.size() + } + + /// The offset of the `callee` field within the `VMLowering` type. + #[inline] + pub fn lowering_callee_offset(&self) -> u32 { + 0 + } + + /// The offset of the `data` field within the `VMLowering` type. + #[inline] + pub fn lowering_data_offset(&self) -> u32 { + u32::from(self.ptr.size()) + } + + /// The offset of the base of the `runtime_memories` field + #[inline] + pub fn runtime_memories(&self) -> u32 { + self.memories + } + + /// The offset of the `*mut VMMemoryDefinition` for the runtime index + /// provided. + #[inline] + pub fn runtime_memory(&self, index: RuntimeMemoryIndex) -> u32 { + assert!(index.as_u32() < self.num_runtime_memories); + self.runtime_memories() + index.as_u32() * u32::from(self.ptr.size()) + } + + /// The offset of the base of the `runtime_reallocs` field + #[inline] + pub fn runtime_reallocs(&self) -> u32 { + self.reallocs + } + + /// The offset of the `*mut VMCallerCheckedAnyfunc` for the runtime index + /// provided. + #[inline] + pub fn runtime_realloc(&self, index: RuntimeReallocIndex) -> u32 { + assert!(index.as_u32() < self.num_runtime_reallocs); + self.runtime_reallocs() + index.as_u32() * u32::from(self.ptr.size()) + } + + /// Return the size of the `VMComponentContext` allocation. + #[inline] + pub fn size_of_vmctx(&self) -> u32 { + self.size + } +} diff --git a/crates/environ/src/vmoffsets.rs b/crates/environ/src/vmoffsets.rs index 9c0b97e80e..ad579d9fd9 100644 --- a/crates/environ/src/vmoffsets.rs +++ b/crates/environ/src/vmoffsets.rs @@ -95,6 +95,32 @@ pub struct VMOffsets

{ pub trait PtrSize { /// Returns the pointer size, in bytes, for the target. fn size(&self) -> u8; + + /// The offset of the `func_ptr` field. + #[allow(clippy::erasing_op)] + #[inline] + fn vmcaller_checked_anyfunc_func_ptr(&self) -> u8 { + 0 * self.size() + } + + /// The offset of the `type_index` field. + #[allow(clippy::identity_op)] + #[inline] + fn vmcaller_checked_anyfunc_type_index(&self) -> u8 { + 1 * self.size() + } + + /// The offset of the `vmctx` field. + #[inline] + fn vmcaller_checked_anyfunc_vmctx(&self) -> u8 { + 2 * self.size() + } + + /// Return the size of `VMCallerCheckedAnyfunc`. + #[inline] + fn size_of_vmcaller_checked_anyfunc(&self) -> u8 { + 3 * self.size() + } } /// Type representing the size of a pointer for the current compilation host @@ -310,7 +336,7 @@ impl From> for VMOffsets

{ = cmul(ret.num_defined_globals, ret.size_of_vmglobal_definition()), size(defined_anyfuncs) = cmul( ret.num_escaped_funcs, - ret.size_of_vmcaller_checked_anyfunc(), + ret.ptr.size_of_vmcaller_checked_anyfunc(), ), } @@ -510,35 +536,6 @@ impl VMOffsets

{ } } -/// Offsets for `VMCallerCheckedAnyfunc`. -impl VMOffsets

{ - /// The offset of the `func_ptr` field. - #[allow(clippy::erasing_op)] - #[inline] - pub fn vmcaller_checked_anyfunc_func_ptr(&self) -> u8 { - 0 * self.pointer_size() - } - - /// The offset of the `type_index` field. - #[allow(clippy::identity_op)] - #[inline] - pub fn vmcaller_checked_anyfunc_type_index(&self) -> u8 { - 1 * self.pointer_size() - } - - /// The offset of the `vmctx` field. - #[inline] - pub fn vmcaller_checked_anyfunc_vmctx(&self) -> u8 { - 2 * self.pointer_size() - } - - /// Return the size of `VMCallerCheckedAnyfunc`. - #[inline] - pub fn size_of_vmcaller_checked_anyfunc(&self) -> u8 { - 3 * self.pointer_size() - } -} - /// Offsets for `VMContext`. impl VMOffsets

{ /// Return the offset to the `magic` value in this `VMContext`. @@ -700,7 +697,7 @@ impl VMOffsets

{ assert!(!index.is_reserved_value()); assert_lt!(index.as_u32(), self.num_escaped_funcs); self.vmctx_anyfuncs_begin() - + index.as_u32() * u32::from(self.size_of_vmcaller_checked_anyfunc()) + + index.as_u32() * u32::from(self.ptr.size_of_vmcaller_checked_anyfunc()) } /// Return the offset to the `body` field in `*const VMFunctionBody` index `index`. diff --git a/crates/runtime/src/vmcontext.rs b/crates/runtime/src/vmcontext.rs index fc650ed9c7..4a53a32999 100644 --- a/crates/runtime/src/vmcontext.rs +++ b/crates/runtime/src/vmcontext.rs @@ -571,7 +571,7 @@ mod test_vmcaller_checked_anyfunc { use super::VMCallerCheckedAnyfunc; use memoffset::offset_of; use std::mem::size_of; - use wasmtime_environ::{Module, VMOffsets}; + use wasmtime_environ::{Module, PtrSize, VMOffsets}; #[test] fn check_vmcaller_checked_anyfunc_offsets() { @@ -579,19 +579,19 @@ mod test_vmcaller_checked_anyfunc { let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module); assert_eq!( size_of::(), - usize::from(offsets.size_of_vmcaller_checked_anyfunc()) + usize::from(offsets.ptr.size_of_vmcaller_checked_anyfunc()) ); assert_eq!( offset_of!(VMCallerCheckedAnyfunc, func_ptr), - usize::from(offsets.vmcaller_checked_anyfunc_func_ptr()) + usize::from(offsets.ptr.vmcaller_checked_anyfunc_func_ptr()) ); assert_eq!( offset_of!(VMCallerCheckedAnyfunc, type_index), - usize::from(offsets.vmcaller_checked_anyfunc_type_index()) + usize::from(offsets.ptr.vmcaller_checked_anyfunc_type_index()) ); assert_eq!( offset_of!(VMCallerCheckedAnyfunc, vmctx), - usize::from(offsets.vmcaller_checked_anyfunc_vmctx()) + usize::from(offsets.ptr.vmcaller_checked_anyfunc_vmctx()) ); } } diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 4e78397dcb..b0fdfa7ac0 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -108,4 +108,7 @@ memory-init-cow = ["wasmtime-runtime/memory-init-cow"] # Enables in-progress support for the component model. Note that this feature is # in-progress, buggy, and incomplete. This is primarily here for internal # testing purposes. -component-model = ["wasmtime-environ/component-model"] +component-model = [ + "wasmtime-environ/component-model", + "wasmtime-cranelift?/component-model", +] diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index f0d7941d4b..350ace22f2 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -1,10 +1,18 @@ +use crate::signatures::SignatureCollection; use crate::{Engine, Module}; use anyhow::{bail, Context, Result}; use std::fs; +use std::ops::Range; use std::path::Path; +use std::ptr::NonNull; use std::sync::Arc; -use wasmtime_environ::component::{ComponentTypes, ModuleUpvarIndex, Translation, Translator}; +use wasmtime_environ::component::{ + ComponentTypes, Initializer, LoweredIndex, ModuleUpvarIndex, TrampolineInfo, Translation, + Translator, +}; use wasmtime_environ::PrimaryMap; +use wasmtime_jit::CodeMemory; +use wasmtime_runtime::VMFunctionBody; /// A compiled WebAssembly Component. // @@ -15,9 +23,36 @@ pub struct Component { } struct ComponentInner { + /// Type information calculated during translation about this component. component: wasmtime_environ::component::Component, + + /// Core wasm modules that the component defined internally, indexed by the + /// compile-time-assigned `ModuleUpvarIndex`. upvars: PrimaryMap, + + /// Registered core wasm signatures of this component, or otherwise the + /// mapping of the component-local `SignatureIndex` to the engine-local + /// `VMSharedSignatureIndex`. + signatures: SignatureCollection, + + /// Type information about this component and all the various types it + /// defines internally. All type indices for `component` will point into + /// this field. types: Arc, + + /// The in-memory ELF image of the compiled trampolines for this component. + /// + /// This is currently only used for wasm-to-host trampolines when + /// `canon.lower` is encountered. + trampoline_obj: CodeMemory, + + /// The index ranges within `trampoline_obj`'s mmap memory for the entire + /// text section. + text: Range, + + /// Where trampolines are located within the `text` section of + /// `trampoline_obj`. + trampolines: PrimaryMap, } impl Component { @@ -84,26 +119,73 @@ impl Component { let Translation { component, upvars, .. } = translation; - let upvars = upvars.into_iter().map(|(_, t)| t).collect::>(); - let upvars = engine - .run_maybe_parallel(upvars, |module| { - let (mmap, info) = Module::compile_functions(engine, module, types.module_types())?; - // FIXME: the `SignatureCollection` here is re-registering the - // entire list of wasm types within `types` on each invocation. - // That's ok semantically but is quite slow to do so. This - // should build up a mapping from `SignatureIndex` to - // `VMSharedSignatureIndex` once and then reuse that for each - // module somehow. - Module::from_parts(engine, mmap, info, types.clone()) - })? - .into_iter() - .collect(); + let (upvars, trampolines) = engine.join_maybe_parallel( + // In one (possibly) parallel task all the modules found within this + // component are compiled. Note that this will further parallelize + // function compilation internally too. + || -> Result<_> { + let upvars = upvars.into_iter().map(|(_, t)| t).collect::>(); + let modules = engine.run_maybe_parallel(upvars, |module| { + let (mmap, info) = + Module::compile_functions(engine, module, types.module_types())?; + // FIXME: the `SignatureCollection` here is re-registering the + // entire list of wasm types within `types` on each invocation. + // That's ok semantically but is quite slow to do so. This + // should build up a mapping from `SignatureIndex` to + // `VMSharedSignatureIndex` once and then reuse that for each + // module somehow. + Module::from_parts(engine, mmap, info, types.clone()) + })?; + + Ok(modules.into_iter().collect::>()) + }, + // In another (possibly) parallel task we compile lowering + // trampolines necessary found in the component. + || -> Result<_> { + let lowerings = component + .initializers + .iter() + .filter_map(|init| match init { + Initializer::LowerImport(i) => Some(i), + _ => None, + }) + .collect::>(); + let compiler = engine.compiler().component_compiler(); + let trampolines = engine + .run_maybe_parallel(lowerings, |lowering| { + compiler.compile_lowered_trampoline(&component, lowering, &types) + })? + .into_iter() + .collect(); + let mut obj = engine.compiler().object()?; + let trampolines = compiler.emit_obj(trampolines, &mut obj)?; + Ok((trampolines, wasmtime_jit::mmap_vec_from_obj(obj)?)) + }, + ); + let upvars = upvars?; + let (trampolines, trampoline_obj) = trampolines?; + let mut trampoline_obj = CodeMemory::new(trampoline_obj); + let code = trampoline_obj.publish()?; + let text = wasmtime_jit::subslice_range(code.text, code.mmap); + + // FIXME: for the same reason as above where each module is + // re-registering everything this should only be registered once. This + // is benign for now but could do with refactorings later on. + let signatures = SignatureCollection::new_for_module( + engine.signatures(), + types.module_types(), + [].into_iter(), + ); Ok(Component { inner: Arc::new(ComponentInner { component, upvars, types, + trampolines, + trampoline_obj, + text, + signatures, }), }) } @@ -119,4 +201,15 @@ impl Component { pub(crate) fn types(&self) -> &Arc { &self.inner.types } + + pub(crate) fn signatures(&self) -> &SignatureCollection { + &self.inner.signatures + } + + pub(crate) fn trampoline_ptr(&self, index: LoweredIndex) -> NonNull { + let info = &self.inner.trampolines[index]; + let text = &self.inner.trampoline_obj.mmap()[self.inner.text.clone()]; + let trampoline = &text[info.start as usize..][..info.length as usize]; + NonNull::new(trampoline.as_ptr() as *mut VMFunctionBody).unwrap() + } } diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 919e229adb..243647b26b 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -246,7 +246,16 @@ impl<'a> Instantiator<'a> { unsafe { crate::Instance::new_started(store, module, imports.as_ref())? }; self.data.instances.push(i); } - Initializer::LowerImport(_) => unimplemented!(), + Initializer::LowerImport(i) => { + drop(self.component.trampoline_ptr(i.index)); + drop( + self.component + .signatures() + .shared_signature(i.canonical_abi) + .unwrap(), + ); + unimplemented!() + } Initializer::ExtractMemory(export) => { let memory = match self.data.lookup_export(store.0, export) { diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index 4886d78910..fbef959231 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -224,6 +224,25 @@ impl Engine { .collect::, E>>() } + /// Executes `f1` and `f2` in parallel if parallel compilation is enabled at + /// both runtime and compile time, otherwise runs them synchronously. + #[allow(dead_code)] // only used for the component-model feature right now + pub(crate) fn join_maybe_parallel( + &self, + f1: impl FnOnce() -> T + Send, + f2: impl FnOnce() -> U + Send, + ) -> (T, U) + where + T: Send, + U: Send, + { + if self.config().parallel_compilation { + #[cfg(feature = "parallel-compilation")] + return rayon::join(f1, f2); + } + (f1(), f2()) + } + /// Returns the target triple which this engine is compiling code for /// and/or running code for. pub(crate) fn target(&self) -> target_lexicon::Triple { diff --git a/tests/all/component_model.rs b/tests/all/component_model.rs index 98387f154a..4d855e7c4a 100644 --- a/tests/all/component_model.rs +++ b/tests/all/component_model.rs @@ -3,6 +3,7 @@ use wasmtime::component::Component; use wasmtime::{Config, Engine}; mod func; +mod import; fn engine() -> Engine { let mut config = Config::new(); diff --git a/tests/all/component_model/import.rs b/tests/all/component_model/import.rs new file mode 100644 index 0000000000..04d7fc40b8 --- /dev/null +++ b/tests/all/component_model/import.rs @@ -0,0 +1,80 @@ +use anyhow::Result; +use wasmtime::component::Component; + +#[test] +fn can_compile() -> Result<()> { + let engine = super::engine(); + let libc = r#" + (module $libc + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + unreachable) + (func (export "canonical_abi_free") (param i32 i32 i32) + unreachable) + ) + (instance $libc (instantiate (module $libc))) + "#; + Component::new( + &engine, + r#"(component + (import "" (func $f)) + (func (canon.lower (func $f))) + )"#, + )?; + Component::new( + &engine, + format!( + r#"(component + (import "" (func $f (param string))) + {libc} + (func (canon.lower (into $libc) (func $f))) + )"# + ), + )?; + Component::new( + &engine, + format!( + r#"(component + (import "f1" (func $f1 (param string) (result string))) + {libc} + (func (canon.lower (into $libc) (func $f1))) + + (import "f2" (func $f2 (param u32) (result (list u8)))) + (instance $libc2 (instantiate (module $libc))) + (func (canon.lower (into $libc2) (func $f2))) + + (func (canon.lower (into $libc2) (func $f1))) + (func (canon.lower (into $libc) (func $f2))) + )"# + ), + )?; + Component::new( + &engine, + format!( + r#"(component + (import "log" (func $log (param string))) + {libc} + (func $log_lower (canon.lower (into $libc) (func $log))) + + (module $logger + (import "host" "log" (func $log (param i32 i32))) + (import "libc" "memory" (memory 1)) + + (func (export "call") + i32.const 0 + i32.const 0 + call $log) + ) + (instance $logger (instantiate (module $logger) + (with "host" (instance (export "log" (func $log_lower)))) + (with "libc" (instance $libc)) + )) + + (func (export "call") + (canon.lift (func) (func $logger "call")) + ) + )"# + ), + )?; + Ok(()) +}