From 3ed6fae7b3e6e2e80c7e89c5a4791d9cb0a0a9f1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 3 Jun 2022 10:01:42 -0500 Subject: [PATCH] Add trampoline compilation support for lowered imports (#4206) * Add trampoline compilation support for lowered imports This commit adds support to the component model implementation for compiling trampolines suitable for calling host imports. Currently this is purely just the compilation side of things, modifying the wasmtime-cranelift crate and additionally filling out a new `VMComponentOffsets` type (similar to `VMOffsets`). The actual creation of a `VMComponentContext` is still not performed and will be a subsequent PR. Internally though some tests are actually possible with this where we at least assert that compilation of a component and creation of everything in-memory doesn't panic or trip any assertions, so some tests are added here for that as well. * Fix some test errors --- crates/cranelift/Cargo.toml | 1 + crates/cranelift/src/compiler.rs | 8 + crates/cranelift/src/compiler/component.rs | 170 +++++++++++++ crates/cranelift/src/func_environ.rs | 8 +- crates/cranelift/src/obj.rs | 2 +- crates/environ/src/compilation.rs | 7 + crates/environ/src/component.rs | 4 + crates/environ/src/component/compiler.rs | 58 +++++ .../src/component/vmcomponent_offsets.rs | 240 ++++++++++++++++++ crates/environ/src/vmoffsets.rs | 59 ++--- crates/runtime/src/vmcontext.rs | 10 +- crates/wasmtime/Cargo.toml | 5 +- crates/wasmtime/src/component/component.rs | 123 +++++++-- crates/wasmtime/src/component/instance.rs | 11 +- crates/wasmtime/src/engine.rs | 19 ++ tests/all/component_model.rs | 1 + tests/all/component_model/import.rs | 80 ++++++ 17 files changed, 748 insertions(+), 58 deletions(-) create mode 100644 crates/cranelift/src/compiler/component.rs create mode 100644 crates/environ/src/component/compiler.rs create mode 100644 crates/environ/src/component/vmcomponent_offsets.rs create mode 100644 tests/all/component_model/import.rs 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(()) +}