From b0e0f27b02baa18ddaca1b5f17612c72bc5f56ff Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 May 2022 11:16:38 -0700 Subject: [PATCH] wip --- crates/cranelift/Cargo.toml | 1 + crates/cranelift/src/compiler.rs | 43 +- crates/cranelift/src/component.rs | 91 ++++ crates/cranelift/src/lib.rs | 2 + crates/cranelift/src/obj.rs | 212 +++++--- crates/environ/src/compilation.rs | 4 + crates/environ/src/component.rs | 4 + crates/environ/src/component/compiler.rs | 33 ++ crates/environ/src/component/info.rs | 52 +- crates/environ/src/component/translate.rs | 507 +++++++++++++----- crates/environ/src/component/types.rs | 26 + .../src/component/vmcomponent_offsets.rs | 118 ++++ crates/runtime/Cargo.toml | 2 + crates/runtime/src/component.rs | 138 +++++ crates/runtime/src/lib.rs | 2 + crates/wasmtime/Cargo.toml | 6 +- crates/wasmtime/src/component/component.rs | 79 ++- crates/wasmtime/src/component/func.rs | 71 +-- crates/wasmtime/src/component/func/host.rs | 199 +++++++ crates/wasmtime/src/component/instance.rs | 47 +- crates/wasmtime/src/component/linker.rs | 150 ++++++ crates/wasmtime/src/component/mod.rs | 5 +- crates/wasmtime/src/engine.rs | 16 + 23 files changed, 1493 insertions(+), 315 deletions(-) create mode 100644 crates/cranelift/src/component.rs create mode 100644 crates/environ/src/component/compiler.rs create mode 100644 crates/environ/src/component/vmcomponent_offsets.rs create mode 100644 crates/runtime/src/component.rs create mode 100644 crates/wasmtime/src/component/func/host.rs create mode 100644 crates/wasmtime/src/component/linker.rs diff --git a/crates/cranelift/Cargo.toml b/crates/cranelift/Cargo.toml index 66e8de85ba62..f24eac92ec83 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 = [] diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index fbe9c0168414..37393ebbdb61 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -7,7 +7,7 @@ use crate::{ CompiledFunction, FunctionAddressMap, Relocation, RelocationTarget, }; use anyhow::{Context as _, Result}; -use cranelift_codegen::ir::{self, ExternalName, InstBuilder, MemFlags}; +use cranelift_codegen::ir::{self, ExternalName, InstBuilder, MemFlags, Type, Value}; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::Context; @@ -50,7 +50,7 @@ impl Default for CompilerContext { /// the Wasm to Compiler IR, optimizing it and then translating to assembly. pub(crate) struct Compiler { contexts: Mutex>, - isa: Box, + pub(crate) isa: Box, linkopts: LinkOptions, } @@ -375,7 +375,9 @@ impl wasmtime_environ::Compiler for Compiler { obj: &mut Object<'static>, ) -> Result<(Trampoline, Trampoline)> { let host_to_wasm = self.host_to_wasm_trampoline(ty)?; - let wasm_to_host = self.wasm_to_host_trampoline(ty, host_fn)?; + let wasm_to_host = self.wasm_to_host_trampoline(ty, true, |builder, _, pointer_type| { + builder.ins().iconst(pointer_type, host_fn as i64) + })?; let module = Module::new(); let mut builder = ObjectBuilder::new(obj, &module, &*self.isa); let a = builder.trampoline(SignatureIndex::new(0), &host_to_wasm); @@ -408,6 +410,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 { @@ -508,17 +515,22 @@ impl Compiler { Ok(func) } - fn wasm_to_host_trampoline( + pub(crate) fn wasm_to_host_trampoline( &self, ty: &WasmFuncType, - host_fn: usize, + give_host_callee_vmcontext: bool, + host_fn: impl FnOnce(&mut FunctionBuilder, Value, Type) -> Value, ) -> Result { let isa = &*self.isa; let pointer_type = isa.pointer_type(); let wasm_signature = indirect_signature(isa, ty); - // The host signature has an added parameter for the `values_vec` input - // and output. let mut host_signature = blank_sig(isa, wasmtime_call_conv(isa)); + if !give_host_callee_vmcontext { + host_signature.params.pop(); + } + // The host signature has an added parameter for the `values_vec` + // input/output as well as the size of the `values_vec`. + host_signature.params.push(ir::AbiParam::new(pointer_type)); host_signature.params.push(ir::AbiParam::new(pointer_type)); // Compute the size of the values vector. The vmctx and caller vmctx are passed separately. @@ -548,8 +560,9 @@ impl Compiler { let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0); let mut mflags = MemFlags::trusted(); mflags.set_endianness(ir::Endianness::Little); + let params_offset = if give_host_callee_vmcontext { 2 } else { 1 }; for i in 0..ty.params().len() { - let val = builder.func.dfg.block_params(block0)[i + 2]; + let val = builder.func.dfg.block_params(block0)[i + params_offset]; builder .ins() .store(mflags, val, values_vec_ptr_val, (i * value_size) as i32); @@ -557,13 +570,21 @@ impl Compiler { let block_params = builder.func.dfg.block_params(block0); let vmctx_ptr_val = block_params[0]; - let caller_vmctx_ptr_val = block_params[1]; - let callee_args = vec![vmctx_ptr_val, caller_vmctx_ptr_val, values_vec_ptr_val]; + let mut callee_args = vec![vmctx_ptr_val]; + if give_host_callee_vmcontext { + callee_args.push(block_params[1]); + } + callee_args.push(values_vec_ptr_val); + callee_args.push( + builder + .ins() + .iconst(pointer_type, i64::from(values_vec_len)), + ); let new_sig = builder.import_signature(host_signature); - let callee_value = builder.ins().iconst(pointer_type, host_fn as i64); + let callee_value = host_fn(&mut builder, vmctx_ptr_val, pointer_type); builder .ins() .call_indirect(new_sig, callee_value, &callee_args); diff --git a/crates/cranelift/src/component.rs b/crates/cranelift/src/component.rs new file mode 100644 index 000000000000..385a6c903b2b --- /dev/null +++ b/crates/cranelift/src/component.rs @@ -0,0 +1,91 @@ +use crate::compiler::Compiler; +use crate::obj::UnwindInfoBuilder; +use crate::CompiledFunction; +use anyhow::Result; +use cranelift_codegen::ir::{InstBuilder, MemFlags}; +use object::write::{Object, StandardSegment, Symbol, SymbolSection}; +use object::{SectionKind, SymbolFlags, SymbolKind, SymbolScope}; +use std::any::Any; +use wasmtime_environ::component::{ + Component, ComponentCompiler, ComponentTypes, LoweredIndex, TrampolineInfo, VMComponentOffsets, +}; +use wasmtime_environ::{EntityRef, PrimaryMap}; + +impl ComponentCompiler for Compiler { + fn compile_lowered_trampoline( + &self, + component: &Component, + index: LoweredIndex, + types: &ComponentTypes, + ) -> Result> { + let ty = component.lowerings[index].canonical_abi; + let ty = &types[ty]; + let offsets = VMComponentOffsets::new(self.isa.pointer_bytes(), component); + let trampoline: CompiledFunction = + self.wasm_to_host_trampoline(ty, false, |builder, vmctx, pointer_type| { + let offset = offsets.lowering_callee(index); + builder.ins().load( + pointer_type, + MemFlags::trusted(), + vmctx, + i32::try_from(offset).unwrap(), + ) + })?; + Ok(Box::new(trampoline)) + } + + fn emit_obj( + &self, + trampolines: PrimaryMap>, + obj: &mut Object<'static>, + ) -> Result> { + let trampolines: PrimaryMap = trampolines + .into_iter() + .map(|(_, f)| *f.downcast().unwrap()) + .collect(); + let mut unwind_info = UnwindInfoBuilder::default(); + let text_section = obj.add_section( + obj.segment_name(StandardSegment::Text).to_vec(), + b".text".to_vec(), + SectionKind::Text, + ); + let mut ret = PrimaryMap::new(); + for (idx, trampoline) in trampolines.iter() { + // Currently there's no relocation processing here but it's also + // not used by trampolines at this time. + assert!(trampoline.relocations.is_empty()); + + let len = trampoline.body.len() as u64; + let off = obj.append_section_data(text_section, &trampoline.body, 1); + + // Adding a symbol in normal wasm modules is used to resolve + // relocations, specifically in the dwarf section, but here we + // don't technically need symbols. Add it anyway for now as perhaps + // a debugging convenience one day. + obj.add_symbol(Symbol { + name: format!("wasm_lowered_trampoline{}", idx.index()).into_bytes(), + value: off, + size: len, + kind: SymbolKind::Text, + scope: SymbolScope::Compilation, + weak: false, + section: SymbolSection::Section(text_section), + flags: SymbolFlags::None, + }); + + if let Some(info) = &trampoline.unwind_info { + unwind_info.push(off, len, info, |data, align| { + obj.append_section_data(text_section, data, align.into()) + }); + } + + let i = ret.push(TrampolineInfo { + start: u32::try_from(off).unwrap(), + length: u32::try_from(len).unwrap(), + }); + assert_eq!(i, idx); + } + + Ok(ret) + } +} diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 0bd91a55f9e2..6f4ddbdbf09a 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -58,6 +58,8 @@ pub use builder::builder; mod builder; mod compiler; +#[cfg(feature = "component-model")] +mod component; mod debug; mod func_environ; mod obj; diff --git a/crates/cranelift/src/obj.rs b/crates/cranelift/src/obj.rs index 08373bb2cf5b..8b394a0bf042 100644 --- a/crates/cranelift/src/obj.rs +++ b/crates/cranelift/src/obj.rs @@ -83,17 +83,8 @@ pub struct ObjectBuilder<'a> { /// The WebAssembly module we're generating code for. module: &'a Module, - windows_unwind_info_id: Option, - - /// Packed form of windows unwind tables which, if present, will get emitted - /// to a windows-specific unwind info section. - windows_unwind_info: Vec, - - systemv_unwind_info_id: Option, - - /// Pending unwinding information for DWARF-based platforms. This is used to - /// build a `.eh_frame` lookalike at the very end of object building. - systemv_unwind_info: Vec<(u64, &'a systemv::UnwindInfo)>, + unwind_section_id: Option, + unwind_info: UnwindInfoBuilder<'a>, /// The corresponding symbol for each function, inserted as they're defined. /// @@ -159,10 +150,8 @@ impl<'a> ObjectBuilder<'a> { module, text_section, func_symbols, - windows_unwind_info_id: None, - windows_unwind_info: Vec::new(), - systemv_unwind_info_id: None, - systemv_unwind_info: Vec::new(), + unwind_section_id: None, + unwind_info: Default::default(), text: isa .text_section_builder((module.functions.len() - module.num_imported_funcs) as u32), added_unwind_info: false, @@ -193,35 +182,10 @@ impl<'a> ObjectBuilder<'a> { flags: SymbolFlags::None, }); - match &func.unwind_info { - // Windows unwind information is preferred to come after the code - // itself. The information is appended here just after the function, - // aligned to 4-bytes as required by Windows. - // - // The location of the unwind info, and the function it describes, - // is then recorded in an unwind info table to get embedded into the - // object at the end of compilation. - Some(UnwindInfo::WindowsX64(info)) => { - // Windows prefers Unwind info after the code -- writing it here. - let unwind_size = info.emit_size(); - let mut unwind_info = vec![0; unwind_size]; - info.emit(&mut unwind_info); - let unwind_off = self.text.append(false, &unwind_info, Some(4)); - self.windows_unwind_info.push(RUNTIME_FUNCTION { - begin: u32::try_from(off).unwrap(), - end: u32::try_from(off + body_len).unwrap(), - unwind_address: u32::try_from(unwind_off).unwrap(), - }); - } - - // System-V is different enough that we just record the unwinding - // information to get processed at a later time. - Some(UnwindInfo::SystemV(info)) => { - self.systemv_unwind_info.push((off, info)); - } - - Some(_) => panic!("some unwind info isn't handled here"), - None => {} + if let Some(info) = &func.unwind_info { + self.unwind_info.push(off, body_len, info, |data, align| { + self.text.append(false, data, Some(align)) + }); } for r in func.relocations.iter() { @@ -343,65 +307,140 @@ impl<'a> ObjectBuilder<'a> { pub fn unwind_info(&mut self) { assert!(!self.added_unwind_info); + self.unwind_section_id = self.unwind_info.append_section(self.obj); + self.added_unwind_info = true; + } + + pub fn finish(&mut self) -> Result<()> { + // Finish up the text section now that we're done adding functions. + let text = self.text.finish(); + self.obj + .section_mut(self.text_section) + .set_data(text, self.isa.code_section_alignment()); + + // This write will align the text section to a page boundary + // and then return the offset at that point. This gives us the full size + // of the text section at that point, after alignment. + let text_section_size = + self.obj + .append_section_data(self.text_section, &[], self.isa.code_section_alignment()); + + if let Some(section_id) = self.unwind_section_id { + self.unwind_info + .write_section(self.isa, self.obj, section_id, text_section_size); + } + + Ok(()) + } +} + +#[derive(Default)] +pub struct UnwindInfoBuilder<'a> { + windows_unwind_info: Vec, + systemv_unwind_info: Vec<(u64, &'a systemv::UnwindInfo)>, +} +impl<'a> UnwindInfoBuilder<'a> { + /// Pushes the unwind information for a function into this builder. + /// + /// The function being described must be located at `function_offset` within + /// the text section itself, and the function's size is specified by + /// `function_len`. + /// + /// The `info` should come from Cranelift itself and this function may + /// append more data to the text section in which case the `append_data` + /// callback will be invoked. The `append_data` callback receives the data + /// to append to the text section as well as the alignment it needs to be + /// written at. The return value of `append_data` should be the offset + /// within the text section for where the data was written. + pub fn push( + &mut self, + function_offset: u64, + function_len: u64, + info: &'a UnwindInfo, + append_data: impl FnOnce(&[u8], u32) -> u64, + ) { + match info { + // Windows unwind information is preferred to come after the code + // itself. The information is appended here just after the function, + // aligned to 4-bytes as required by Windows. + // + // The location of the unwind info, and the function it describes, + // is then recorded in an unwind info table to get embedded into the + // object at the end of compilation. + UnwindInfo::WindowsX64(info) => { + // Windows prefers Unwind info after the code -- writing it here. + let unwind_size = info.emit_size(); + let mut unwind_info = vec![0; unwind_size]; + info.emit(&mut unwind_info); + let unwind_off = append_data(&unwind_info, 4); + self.windows_unwind_info.push(RUNTIME_FUNCTION { + begin: u32::try_from(function_offset).unwrap(), + end: u32::try_from(function_offset + function_len).unwrap(), + unwind_address: u32::try_from(unwind_off).unwrap(), + }); + } + + // System-V is different enough that we just record the unwinding + // information to get processed at a later time. + UnwindInfo::SystemV(info) => { + self.systemv_unwind_info.push((function_offset, info)); + } + + _ => panic!("some unwind info isn't handled here"), + } + } + + pub fn append_section(&self, obj: &mut Object<'_>) -> Option { if self.windows_unwind_info.len() > 0 { - let segment = self.obj.segment_name(StandardSegment::Data).to_vec(); - self.windows_unwind_info_id = Some(self.obj.add_section( + assert!(self.systemv_unwind_info.len() == 0); + let segment = obj.segment_name(StandardSegment::Data).to_vec(); + return Some(obj.add_section( segment, b"_wasmtime_winx64_unwind".to_vec(), SectionKind::ReadOnlyData, )); } if self.systemv_unwind_info.len() > 0 { - let segment = self.obj.segment_name(StandardSegment::Data).to_vec(); - self.systemv_unwind_info_id = Some(self.obj.add_section( + let segment = obj.segment_name(StandardSegment::Data).to_vec(); + return Some(obj.add_section( segment, b".eh_frame".to_vec(), SectionKind::ReadOnlyData, )); } - self.added_unwind_info = true; + None } - pub fn finish(&mut self) -> Result<()> { - // Finish up the text section now that we're done adding functions. - let text = self.text.finish(); - self.obj - .section_mut(self.text_section) - .set_data(text, self.isa.code_section_alignment()); - - // With all functions added we can also emit the fully-formed unwinding - // information sections. + pub fn write_section( + &mut self, + isa: &dyn TargetIsa, + obj: &mut Object<'_>, + section_id: SectionId, + text_section_size: u64, + ) { if self.windows_unwind_info.len() > 0 { - self.append_windows_unwind_info(); - } - if self.systemv_unwind_info.len() > 0 { - self.append_systemv_unwind_info(); + self.write_windows_unwind_info(obj, section_id) + } else { + self.write_systemv_unwind_info(isa, obj, section_id, text_section_size) } - - Ok(()) } /// This function appends a nonstandard section to the object which is only - /// used during `CodeMemory::allocate_for_object`. + /// used during `CodeMemory::publish`. /// /// This custom section effectively stores a `[RUNTIME_FUNCTION; N]` into /// the object file itself. This way registration of unwind info can simply /// pass this slice to the OS itself and there's no need to recalculate /// anything on the other end of loading a module from a precompiled object. - fn append_windows_unwind_info(&mut self) { + /// + /// Support for reading this is in `crates/jit/src/unwind/winx64.rs`. + fn write_windows_unwind_info(&mut self, obj: &mut Object<'_>, section_id: SectionId) { // Currently the binary format supported here only supports // little-endian for x86_64, or at least that's all where it's tested. // This may need updates for other platforms. - assert_eq!(self.obj.architecture(), Architecture::X86_64); - - let section_id = self.windows_unwind_info_id.unwrap(); - - // Page-align the text section so the unwind info can reside on a - // separate page that doesn't need executable permissions. - self.obj - .append_section_data(self.text_section, &[], self.isa.code_section_alignment()); + assert_eq!(obj.architecture(), Architecture::X86_64); let mut unwind_info = Vec::with_capacity(self.windows_unwind_info.len() * 3 * 4); for info in self.windows_unwind_info.iter() { @@ -409,7 +448,7 @@ impl<'a> ObjectBuilder<'a> { unwind_info.extend_from_slice(&info.end.to_le_bytes()); unwind_info.extend_from_slice(&info.unwind_address.to_le_bytes()); } - self.obj.append_section_data(section_id, &unwind_info, 4); + obj.append_section_data(section_id, &unwind_info, 4); } /// This function appends a nonstandard section to the object which is only @@ -450,22 +489,20 @@ impl<'a> ObjectBuilder<'a> { /// This allows `.eh_frame` to have different virtual memory permissions, /// such as being purely read-only instead of read/execute like the code /// bits. - fn append_systemv_unwind_info(&mut self) { - let section_id = self.systemv_unwind_info_id.unwrap(); - let mut cie = self - .isa + fn write_systemv_unwind_info( + &mut self, + isa: &dyn TargetIsa, + obj: &mut Object<'_>, + section_id: SectionId, + text_section_size: u64, + ) { + let mut cie = isa .create_systemv_cie() .expect("must be able to create a CIE for system-v unwind info"); let mut table = FrameTable::default(); cie.fde_address_encoding = gimli::constants::DW_EH_PE_pcrel; let cie_id = table.add_cie(cie); - // This write will align the text section to a page boundary - // and then return the offset at that point. This gives us the full size - // of the text section at that point, after alignment. - let text_section_size = - self.obj - .append_section_data(self.text_section, &[], self.isa.code_section_alignment()); for (text_section_off, unwind_info) in self.systemv_unwind_info.iter() { let backwards_off = text_section_size - text_section_off; let actual_offset = -i64::try_from(backwards_off).unwrap(); @@ -476,7 +513,7 @@ impl<'a> ObjectBuilder<'a> { let fde = unwind_info.to_fde(Address::Constant(actual_offset as u64)); table.add_fde(cie_id, fde); } - let endian = match self.isa.triple().endianness().unwrap() { + let endian = match isa.triple().endianness().unwrap() { target_lexicon::Endianness::Little => RunTimeEndian::Little, target_lexicon::Endianness::Big => RunTimeEndian::Big, }; @@ -487,8 +524,7 @@ impl<'a> ObjectBuilder<'a> { // a 0 is written at the end of the table for those implementations. let mut endian_vec = (eh_frame.0).0; endian_vec.write_u32(0).unwrap(); - self.obj - .append_section_data(section_id, endian_vec.slice(), 1); + obj.append_section_data(section_id, endian_vec.slice(), 1); use gimli::constants; use gimli::write::Error; diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index bd43d15d342f..0344cb243652 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -232,6 +232,10 @@ pub trait Compiler: Send + Sync { /// Same as [`Compiler::flags`], but ISA-specific (a cranelift-ism) fn isa_flags(&self) -> BTreeMap; + + /// TODO + #[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 3295d34975d9..20bb2ec9931a 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 000000000000..48fd65c4ce02 --- /dev/null +++ b/crates/environ/src/component/compiler.rs @@ -0,0 +1,33 @@ +use crate::component::{Component, ComponentTypes, LoweredIndex}; +use crate::PrimaryMap; +use anyhow::Result; +use object::write::Object; +use serde::{Deserialize, Serialize}; +use std::any::Any; + +/// TODO +#[derive(Serialize, Deserialize)] +pub struct TrampolineInfo { + /// TODO + pub start: u32, + /// TODO + pub length: u32, +} + +/// TODO +pub trait ComponentCompiler: Send + Sync { + /// TODO + fn compile_lowered_trampoline( + &self, + component: &Component, + index: LoweredIndex, + types: &ComponentTypes, + ) -> Result>; + + /// TODO + fn emit_obj( + &self, + trampolines: PrimaryMap>, + obj: &mut Object<'static>, + ) -> Result>; +} diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index 28b60a8ee318..9e9339c1bd14 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -20,7 +20,7 @@ // everything except imported core wasm modules. use crate::component::*; -use crate::{EntityIndex, PrimaryMap}; +use crate::{EntityIndex, PrimaryMap, SignatureIndex}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -37,7 +37,12 @@ use serde::{Deserialize, Serialize}; pub struct Component { /// A list of typed values that this component imports, indexed by either /// the import's position or the name of the import. - pub imports: IndexMap, + pub import_types: PrimaryMap, + /// TODO + pub imports: PrimaryMap)>, + + /// TODO + pub lowerings: PrimaryMap, /// A list of this component's exports, indexed by either position or name. pub exports: IndexMap, @@ -60,7 +65,7 @@ pub enum Instantiation { /// The module index which is being instantiated. module: ModuleUpvarIndex, /// The flat list of arguments to the module's instantiation. - args: Box<[CoreExport]>, + args: Box<[CoreDef]>, }, /// A module import is being instantiated. @@ -71,12 +76,27 @@ pub enum Instantiation { /// if that will work out). ModuleImport { /// Which module import is being instantiated. - import_index: usize, + import: RuntimeImportIndex, /// The flat list of arguments to the module's instantiation. - args: Box<[CoreExport]>, + args: Box<[CoreDef]>, }, } +/// TODO +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CoreDef { + /// TODO + Export(CoreExport), + /// TODO + Lowered(LoweredIndex), +} + +impl From> for CoreDef { + fn from(export: CoreExport) -> CoreDef { + CoreDef::Export(export) + } +} + /// Identifier of an exported item from a core WebAssembly module instance. /// /// Note that the `T` here is the index type for exports which can be @@ -120,6 +140,13 @@ pub enum Export { /// A lifted function being exported which is an adaptation of a core wasm /// function. LiftedFunction(LiftedFunction), + /// TODO + Reexport { + /// TODO + ty: FuncTypeIndex, + /// TODO + import: RuntimeImportIndex, + }, } /// Description of a lifted function. @@ -136,6 +163,17 @@ pub struct LiftedFunction { pub options: CanonicalOptions, } +/// TODO +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LoweredFunction { + /// TODO + pub import: RuntimeImportIndex, + /// TODO + pub canonical_abi: SignatureIndex, + /// TODO + pub options: CanonicalOptions, +} + /// Canonical ABI options associated with a lifted function. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CanonicalOptions { @@ -173,11 +211,11 @@ pub struct Intrinsics { pub memory: CoreExport, /// A memory allocation, and reallocation, function. - pub canonical_abi_realloc: CoreExport, + pub canonical_abi_realloc: CoreDef, /// A memory deallocation function. /// /// NB: this will probably be replaced with a per-export-destructor rather /// than a general memory deallocation function. - pub canonical_abi_free: CoreExport, + pub canonical_abi_free: CoreDef, } diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 15df12078edd..37359c4d1ffb 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -1,5 +1,8 @@ use crate::component::*; -use crate::{EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, Tunables}; +use crate::{ + EntityIndex, EntityType, ModuleEnvironment, ModuleTranslation, PrimaryMap, SignatureIndex, + Tunables, +}; use anyhow::{bail, Result}; use std::collections::HashMap; use std::mem; @@ -28,6 +31,8 @@ pub struct Translation<'data> { /// are referred to within types in `Component`. pub upvars: PrimaryMap>, + import_map: HashMap, + // Index spaces which are built-up during translation but do not persist to // runtime. These are used to understand the structure of the component and // where items come from but at this time these index spaces and their @@ -54,10 +59,13 @@ pub struct Translation<'data> { /// Core wasm tables, always sourced from a previously module instance. tables: PrimaryMap>, + + /// TODO + signatures_to_fill: Vec<(LoweredIndex, FuncIndex)>, } /// How a module is defined within a component. -#[derive(Debug)] +#[derive(Debug, Clone)] enum ModuleDef { /// This module is defined as an "upvar" or a closed over variable /// implicitly available for the component. @@ -71,29 +79,42 @@ enum ModuleDef { /// nothing is known about it except for its type. The `import_index` /// provided here indexes into the `Component`'s import list. Import { - type_idx: ModuleTypeIndex, - import_index: usize, + ty: ModuleTypeIndex, + import: RuntimeImport, }, } -#[derive(Debug)] +#[derive(Debug, Clone)] enum InstanceDef<'data> { Module { instance: RuntimeInstanceIndex, module: ModuleIndex, }, ModuleSynthetic(HashMap<&'data str, EntityIndex>), + Import { + ty: ComponentInstanceTypeIndex, + import: RuntimeImport, + }, + ComponentSynthetic(HashMap<&'data str, ComponentItem>), } /// Source of truth for where a core wasm item comes from. #[derive(Clone)] enum Func<'data> { + // component functions + Import { + ty: FuncTypeIndex, + import: RuntimeImport, + }, Lifted { ty: FuncTypeIndex, func: CoreSource<'data>, options: CanonicalOptions, }, + + // core function Core(CoreSource<'data>), + Lowered(LoweredIndex), } /// Source of truth for where a core wasm item comes from. @@ -120,6 +141,23 @@ enum Action { Done, } +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct RuntimeImport { + source: ImportIndex, + exports: Vec, +} + +impl RuntimeImport { + fn append(&self, name: &str) -> RuntimeImport { + let mut exports = self.exports.clone(); + exports.push(name.to_string()); + RuntimeImport { + source: self.source, + exports, + } + } +} + impl<'a, 'data> Translator<'a, 'data> { /// Creates a new translation state ready to translate a component. pub fn new( @@ -189,7 +227,21 @@ impl<'a, 'data> Translator<'a, 'data> { } Payload::End(offset) => { - self.validator.end(offset)?; + let types = self.validator.end(offset)?; + + // With type information in hand fill in the canonical abi type + // of lowered functions. + for (lower, func) in self.result.signatures_to_fill.drain(..) { + let ty = &mut self.result.component.lowerings[lower].canonical_abi; + assert!(ty.as_u32() == 0); + self.types.module_types_builder().wasm_func_type( + types + .function_at(func.as_u32()) + .expect("should be in-bounds") + .clone() + .try_into()?, + ); + } // When leaving a module be sure to pop the types scope to // ensure that when we go back to the previous module outer @@ -224,30 +276,32 @@ impl<'a, 'data> Translator<'a, 'data> { let import = import?; let ty = TypeIndex::from_u32(import.ty); let ty = self.types.component_outer_type(0, ty); - let (import_index, prev) = self + let source = self .result .component - .imports - .insert_full(import.name.to_string(), ty); - assert!(prev.is_none()); + .import_types + .push((import.name.to_string(), ty)); + let import = RuntimeImport { + source, + exports: Vec::new(), + }; match ty { - TypeDef::Module(type_idx) => { - self.result.modules.push(ModuleDef::Import { - type_idx, - import_index, - }); + TypeDef::Module(ty) => { + self.result.modules.push(ModuleDef::Import { ty, import }); } - TypeDef::Component(_) => { - unimplemented!("component imports"); + TypeDef::ComponentInstance(ty) => { + self.result + .instances + .push(InstanceDef::Import { ty, import }); } - TypeDef::ComponentInstance(_) => { - unimplemented!("component instance imports"); + TypeDef::Func(ty) => { + self.result.funcs.push(Func::Import { ty, import }); } - TypeDef::Func(_) => { - unimplemented!("function imports"); + TypeDef::Component(_) => { + unimplemented!("imports of components"); } TypeDef::Interface(_) => { - unimplemented!("interface type imports"); + unimplemented!("imports of types"); } } } @@ -270,8 +324,8 @@ impl<'a, 'data> Translator<'a, 'data> { func_index, options, } => { - drop((func_index, options)); - unimplemented!("lowered functions"); + let func = FuncIndex::from_u32(func_index); + self.lower_function(func, &options) } }; self.result.funcs.push(func); @@ -317,8 +371,7 @@ impl<'a, 'data> Translator<'a, 'data> { unimplemented!("instantiating a component"); } wasmparser::Instance::ComponentFromExports(exports) => { - drop(exports); - unimplemented!("instantiating a component"); + self.component_instance_from_exports(&exports) } }; self.result.instances.push(instance); @@ -379,20 +432,21 @@ impl<'a, 'data> Translator<'a, 'data> { } } - let instantiation = match self.result.modules[module] { + let instantiation = match self.result.modules[module].clone() { // For modules which we are statically aware of we can look at the // exact order of imports required and build up a list of arguemnts // in that order. This will be fast at runtime because all we have // to do is build up the import lists for instantiation, no name // lookups necessary. ModuleDef::Upvar(upvar_idx) => { - let trans = &self.result.upvars[upvar_idx]; + let imports = self.result.upvars[upvar_idx] + .module + .imports() + .map(|(m, n, _)| (m.to_string(), n.to_string())) + .collect(); Instantiation::ModuleUpvar { module: upvar_idx, - args: self.module_instance_args( - &instance_by_name, - trans.module.imports().map(|(m, n, _)| (m, n)), - ), + args: self.module_instance_args(&instance_by_name, imports), } } @@ -400,17 +454,12 @@ impl<'a, 'data> Translator<'a, 'data> { // order of the imports listed. Note that this will need to be // reshuffled at runtime since the actual module being instantiated // may originally have required imports in a different order. - ModuleDef::Import { - type_idx, - import_index, - } => { - let ty = &self.types[type_idx]; + ModuleDef::Import { ty, import } => { + let import = self.runtime_import_index(import); + let imports = self.types[ty].imports.keys().cloned().collect(); Instantiation::ModuleImport { - import_index, - args: self.module_instance_args( - &instance_by_name, - ty.imports.keys().map(|(a, b)| (a.as_str(), b.as_str())), - ), + import, + args: self.module_instance_args(&instance_by_name, imports), } } }; @@ -424,57 +473,77 @@ impl<'a, 'data> Translator<'a, 'data> { /// The `instance_by_name` map is used go go from the module name of an /// import to the instance that's satisfying the import. The `name` field /// of the import is then looked up within the instance's own exports. - fn module_instance_args<'b>( - &self, + fn module_instance_args( + &mut self, instance_by_name: &HashMap<&'data str, InstanceIndex>, - iter: impl Iterator, - ) -> Box<[CoreExport]> { - iter.map(|(module, name)| { - self.lookup_core_source(instance_by_name[module], name) - .to_core_export(|i| i) - }) - .collect() + imports: Vec<(String, String)>, + ) -> Box<[CoreDef]> { + imports + .iter() + .map(|(module, name)| self.lookup_core_def(instance_by_name[module.as_str()], name)) + .collect() } - /// Looks up the `CoreSource` corresponding to the export `name` of the - /// `module` specified. + /// Calculate the `CoreDef`, a definition of a core wasm item, corresponding + /// to the export `name` of the `instance` specified. /// - /// This classifies the export of the module as either one which we - /// statically know by index within the module itself (because we know the - /// module), or one that must be referred to by name. - fn lookup_core_source(&self, instance: InstanceIndex, name: &'data str) -> CoreSource<'data> { + /// This classifies the export of the instance as one which we + /// statically know by index within an instantiated module (because + /// we know the module), one that must be referred to by name since the + /// module isn't known, or it's a synthesized lowering or adapter of a + /// component function. + fn lookup_core_def(&mut self, instance: InstanceIndex, name: &str) -> CoreDef { match &self.result.instances[instance] { - // The `instance` points to an instantiated module... - InstanceDef::Module { module, instance } => match self.result.modules[*module] { - // ... and the module instantiated is one that we statically - // know the structure of. This means that `name` points to an - // exact index of an item within the module which we lookup here - // and record. - ModuleDef::Upvar(upvar_idx) => { - let trans = &self.result.upvars[upvar_idx]; - CoreSource::Index(*instance, trans.module.exports[name]) - } - - // ... and the module instantiated is imported so we don't - // statically know its structure. This means taht the export - // must be identified by name. - ModuleDef::Import { .. } => CoreSource::Export(*instance, name), - }, + InstanceDef::Module { module, instance } => { + let (src, _ty) = self.lookup_core_source_in_module(*instance, *module, name); + src.to_core_def() + } - // The `instance `points to a "synthetic" instance created in the - // component as a collection of named items from other instances. - // This means that we're simply copying over the original source of - // the item in the first place. InstanceDef::ModuleSynthetic(defs) => match defs[&name] { - EntityIndex::Function(f) => match &self.result.funcs[f] { - Func::Core(c) => c.clone(), + EntityIndex::Function(f) => match self.result.funcs[f].clone() { + Func::Core(c) => c.to_core_def(), + Func::Lowered(i) => CoreDef::Lowered(i), + // should not be possible to hit with a valid component - Func::Lifted { .. } => unreachable!(), + Func::Lifted { .. } | Func::Import { .. } => unreachable!(), }, - EntityIndex::Global(g) => self.result.globals[g].clone(), - EntityIndex::Table(t) => self.result.tables[t].clone(), - EntityIndex::Memory(m) => self.result.memories[m].clone(), + EntityIndex::Global(g) => self.result.globals[g].to_core_def(), + EntityIndex::Table(t) => self.result.tables[t].to_core_def(), + EntityIndex::Memory(m) => self.result.memories[m].to_core_def(), }, + + // should not be possible to hit with a valid component + InstanceDef::Import { .. } | InstanceDef::ComponentSynthetic(_) => unreachable!(), + } + } + + fn lookup_core_source_in_module<'b>( + &self, + instance: RuntimeInstanceIndex, + module: ModuleIndex, + name: &'b str, + ) -> (CoreSource<'b>, EntityType) { + match self.result.modules[module] { + // ... and the module instantiated is one that we statically + // know the structure of. This means that `name` points to an + // exact index of an item within the module which we lookup here + // and record. + ModuleDef::Upvar(upvar_idx) => { + let trans = &self.result.upvars[upvar_idx]; + let idx = trans.module.exports[name]; + let src = CoreSource::Index(instance, idx); + let ty = trans.module.type_of(idx); + (src, ty) + } + + // ... and the module instantiated is imported so we don't + // statically know its structure. This means taht the export + // must be identified by name. + ModuleDef::Import { ty, .. } => { + let src = CoreSource::Export(instance, name); + let ty = self.types[ty].exports[name].clone(); + (src, ty) + } } } @@ -505,53 +574,94 @@ impl<'a, 'data> Translator<'a, 'data> { } // doesn't get past validation - wasmparser::ExternalKind::Tag => unimplemented!(), + wasmparser::ExternalKind::Tag => unimplemented!("wasm exceptions"), }; map.insert(export.name, idx); } InstanceDef::ModuleSynthetic(map) } + /// Creates a synthetic module from the list of items currently in the + /// module and their given names. + fn component_instance_from_exports( + &mut self, + exports: &[wasmparser::ComponentExport<'data>], + ) -> InstanceDef<'data> { + let mut map = HashMap::with_capacity(exports.len()); + for export in exports { + let idx = match &export.kind { + wasmparser::ComponentArgKind::Function(i) => { + let index = FuncIndex::from_u32(*i); + ComponentItem::Func(index) + } + wasmparser::ComponentArgKind::Module(i) => { + let index = ModuleIndex::from_u32(*i); + ComponentItem::Module(index) + } + wasmparser::ComponentArgKind::Instance(i) => { + let index = InstanceIndex::from_u32(*i); + ComponentItem::Instance(index) + } + wasmparser::ComponentArgKind::Component(i) => { + let index = ComponentIndex::from_u32(*i); + ComponentItem::Component(index) + } + wasmparser::ComponentArgKind::Value(_) => { + unimplemented!("component values"); + } + wasmparser::ComponentArgKind::Type(_) => { + unimplemented!("component type export"); + } + }; + map.insert(export.name, idx); + } + InstanceDef::ComponentSynthetic(map) + } + fn export(&mut self, export: &wasmparser::ComponentExport<'data>) { let name = export.name; let export = match export.kind { wasmparser::ComponentExportKind::Module(i) => { let idx = ModuleIndex::from_u32(i); drop(idx); - unimplemented!("unimplemented module export"); + unimplemented!("exporting a module"); } wasmparser::ComponentExportKind::Component(i) => { let idx = ComponentIndex::from_u32(i); drop(idx); - unimplemented!("unimplemented component export"); + unimplemented!("exporting a component"); } wasmparser::ComponentExportKind::Instance(i) => { let idx = InstanceIndex::from_u32(i); drop(idx); - unimplemented!("unimplemented instance export"); + unimplemented!("exporting an instance"); } wasmparser::ComponentExportKind::Function(i) => { let idx = FuncIndex::from_u32(i); - match &self.result.funcs[idx] { + match self.result.funcs[idx].clone() { Func::Lifted { ty, func, options } => Export::LiftedFunction(LiftedFunction { - ty: *ty, + ty, func: func.to_core_export(|i| match i { EntityIndex::Function(i) => i, _ => unreachable!(), }), - options: options.clone(), + options, }), + Func::Import { ty, import } => Export::Reexport { + ty, + import: self.runtime_import_index(import), + }, // should not be possible to hit with a valid module. - Func::Core(_) => unreachable!(), + Func::Core(_) | Func::Lowered(_) => unreachable!(), } } wasmparser::ComponentExportKind::Value(_) => { - unimplemented!("unimplemented value export"); + unimplemented!("exporting a value"); } wasmparser::ComponentExportKind::Type(i) => { let idx = TypeIndex::from_u32(i); drop(idx); - unimplemented!("unimplemented value export"); + unimplemented!("exporting a type"); } }; self.result @@ -568,11 +678,7 @@ impl<'a, 'data> Translator<'a, 'data> { name, } => { let instance = InstanceIndex::from_u32(*instance); - match &self.result.instances[instance] { - InstanceDef::Module { .. } | InstanceDef::ModuleSynthetic(_) => { - self.alias_module_instance_export(*kind, instance, name); - } - } + self.alias_instance_export(*kind, instance, name); } wasmparser::Alias::OuterModule { .. } => { unimplemented!("alias outer module"); @@ -597,36 +703,99 @@ impl<'a, 'data> Translator<'a, 'data> { } } - /// Inserts an item in to the relevant namespace aliasing the `name`'d - /// export of the `instance` provided. - fn alias_module_instance_export( + /// TODO + fn alias_instance_export( &mut self, kind: wasmparser::AliasKind, instance: InstanceIndex, name: &'data str, ) { - let src = self.lookup_core_source(instance, name); - match kind { - wasmparser::AliasKind::Func => { - self.result.funcs.push(Func::Core(src)); - } - wasmparser::AliasKind::Global => { - self.result.globals.push(src); - } - wasmparser::AliasKind::Memory => { - self.result.memories.push(src); - } - wasmparser::AliasKind::Table => { - self.result.tables.push(src); + match &self.result.instances[instance] { + // The `instnace` points to an imported component instance>.. + InstanceDef::Import { import, ty } => { + let import = import.append(name); + match self.types[*ty].exports[name] { + TypeDef::Module(ty) => { + assert_eq!(kind, wasmparser::AliasKind::Module); + self.result.modules.push(ModuleDef::Import { import, ty }); + } + TypeDef::ComponentInstance(ty) => { + assert_eq!(kind, wasmparser::AliasKind::Instance); + self.result + .instances + .push(InstanceDef::Import { import, ty }); + } + TypeDef::Func(ty) => { + assert_eq!(kind, wasmparser::AliasKind::ComponentFunc); + self.result.funcs.push(Func::Import { import, ty }); + } + TypeDef::Interface(_) => unimplemented!("alias type export"), + TypeDef::Component(_) => unimplemented!("alias component export"), + } } - other => { - panic!("unknown/unimplemented alias kind {other:?}"); + + // The `instance` points to an instantiated module... + InstanceDef::Module { instance, module } => { + let (src, ty) = self.lookup_core_source_in_module(*instance, *module, name); + match ty { + EntityType::Function(_) => { + self.result.funcs.push(Func::Core(src)); + } + EntityType::Global(_) => { + self.result.globals.push(src); + } + EntityType::Memory(_) => { + self.result.memories.push(src); + } + EntityType::Table(_) => { + self.result.tables.push(src); + } + EntityType::Tag(_) => unimplemented!("wasm exceptions"), + } } + + // For synthetic component/module instances we can just copy the + // definition of the original item into a new slot as well to record + // that the index describes the same item. + InstanceDef::ComponentSynthetic(exports) => match exports[&name] { + ComponentItem::Func(i) => { + assert_eq!(kind, wasmparser::AliasKind::ComponentFunc); + self.result.funcs.push(self.result.funcs[i].clone()); + } + ComponentItem::Module(i) => { + assert_eq!(kind, wasmparser::AliasKind::Module); + self.result.modules.push(self.result.modules[i].clone()); + } + ComponentItem::Instance(i) => { + assert_eq!(kind, wasmparser::AliasKind::Instance); + self.result.instances.push(self.result.instances[i].clone()); + } + ComponentItem::Component(_) => unimplemented!("aliasing a component export"), + }, + + InstanceDef::ModuleSynthetic(exports) => match exports[&name] { + EntityIndex::Function(i) => { + assert_eq!(kind, wasmparser::AliasKind::Func); + self.result.funcs.push(self.result.funcs[i].clone()); + } + EntityIndex::Global(i) => { + assert_eq!(kind, wasmparser::AliasKind::Global); + self.result.globals.push(self.result.globals[i].clone()); + } + EntityIndex::Table(i) => { + assert_eq!(kind, wasmparser::AliasKind::Table); + self.result.tables.push(self.result.tables[i].clone()); + } + EntityIndex::Memory(i) => { + assert_eq!(kind, wasmparser::AliasKind::Memory); + self.result.memories.push(self.result.memories[i].clone()); + } + }, } } fn lift_function( - &self, + &mut self, ty: TypeIndex, func: FuncIndex, options: &[wasmparser::CanonicalOption], @@ -638,14 +807,50 @@ impl<'a, 'data> Translator<'a, 'data> { }; let func = match &self.result.funcs[func] { Func::Core(core) => core.clone(), + + Func::Lowered(_) => unimplemented!("lifting a lowered function"), + // should not be possible after validation - Func::Lifted { .. } => unreachable!(), + Func::Lifted { .. } | Func::Import { .. } => unreachable!(), }; let options = self.canonical_options(options); Func::Lifted { ty, func, options } } - fn canonical_options(&self, opts: &[wasmparser::CanonicalOption]) -> CanonicalOptions { + fn lower_function( + &mut self, + func: FuncIndex, + options: &[wasmparser::CanonicalOption], + ) -> Func<'data> { + let options = self.canonical_options(options); + match self.result.funcs[func].clone() { + Func::Import { import, .. } => { + let import = self.runtime_import_index(import); + // This is filled after the component is finished when we have + // wasmparser's type information available, so leave a dummy + // for now to get filled in. + let canonical_abi = SignatureIndex::from_u32(0); + let idx = self.result.component.lowerings.push(LoweredFunction { + import, + options, + canonical_abi, + }); + self.result.signatures_to_fill.push((idx, func)); + Func::Lowered(idx) + } + + // From reading the spec, this technically should create a function + // that lifts the arguments and then afterwards unconditionally + // traps. That would mean that this validates the arguments within + // the context of `options` and then traps. + Func::Lifted { .. } => unimplemented!("lower a lifted function"), + + // should not be possible after validation + Func::Core(_) | Func::Lowered(_) => unreachable!(), + } + } + + fn canonical_options(&mut self, opts: &[wasmparser::CanonicalOption]) -> CanonicalOptions { let mut ret = CanonicalOptions::default(); for opt in opts { match opt { @@ -663,24 +868,15 @@ impl<'a, 'data> Translator<'a, 'data> { // Note that the `unreachable!()` should not happen for // components which have passed validation. - let memory = self - .lookup_core_source(instance, "memory") - .to_core_export(|i| match i { - EntityIndex::Memory(i) => i, - _ => unreachable!(), - }); - let canonical_abi_free = self - .lookup_core_source(instance, "canonical_abi_free") - .to_core_export(|i| match i { - EntityIndex::Function(i) => i, - _ => unreachable!(), - }); - let canonical_abi_realloc = self - .lookup_core_source(instance, "canonical_abi_realloc") - .to_core_export(|i| match i { - EntityIndex::Function(i) => i, - _ => unreachable!(), - }); + let memory = + self.lookup_core_def(instance, "memory") + .unwrap_export(|i| match i { + EntityIndex::Memory(i) => i, + _ => unreachable!(), + }); + let canonical_abi_free = self.lookup_core_def(instance, "canonical_abi_free"); + let canonical_abi_realloc = + self.lookup_core_def(instance, "canonical_abi_realloc"); ret.intrinsics = Some(Intrinsics { memory, canonical_abi_free, @@ -691,6 +887,19 @@ impl<'a, 'data> Translator<'a, 'data> { } return ret; } + + fn runtime_import_index(&mut self, import: RuntimeImport) -> RuntimeImportIndex { + if let Some(idx) = self.result.import_map.get(&import) { + return *idx; + } + let idx = self + .result + .component + .imports + .push((import.source, import.exports.clone())); + self.result.import_map.insert(import, idx); + return idx; + } } impl CoreSource<'_> { @@ -706,4 +915,28 @@ impl CoreSource<'_> { }, } } + + fn to_core_def(&self) -> CoreDef { + self.to_core_export(|i| i).into() + } +} + +impl CoreDef { + fn unwrap_export(self, get_index: impl FnOnce(EntityIndex) -> T) -> CoreExport { + let export = match self { + CoreDef::Export(export) => export, + CoreDef::Lowered(_) => unreachable!(), + }; + let instance = export.instance; + match export.item { + ExportItem::Index(idx) => CoreExport { + instance, + item: ExportItem::Index(get_index(idx)), + }, + ExportItem::Name(name) => CoreExport { + instance, + item: ExportItem::Name(name), + }, + } + } } diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 1eeddb2cf494..5a92b202565b 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -98,12 +98,28 @@ indices! { /// modules, and this index indexes, at runtime, which of the upvars is /// referenced. pub struct ModuleUpvarIndex(u32); + + /// TODO + pub struct ImportIndex(u32); + /// TODO + pub struct RuntimeImportIndex(u32); + /// TODO + pub struct LoweredIndex(u32); } // Reexport for convenience some core-wasm indices which are also used in the // component model, typically for when aliasing exports of core wasm modules. pub use crate::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeIndex}; +#[derive(Debug, Clone, Copy)] +#[allow(missing_docs)] // TODO +pub enum ComponentItem { + Func(FuncIndex), + Module(ModuleIndex), + Instance(InstanceIndex), + Component(ComponentIndex), +} + /// Runtime information about the type information contained within a component. /// /// One of these is created per top-level component which describes all of the @@ -160,6 +176,16 @@ impl_index! { impl Index for ComponentTypes { ExpectedType => expecteds } } +impl Index for ComponentTypes +where + ModuleTypes: Index, +{ + type Output = >::Output; + fn index(&self, idx: T) -> &Self::Output { + self.module_types.index(idx) + } +} + /// Structured used to build a [`ComponentTypes`] during translation. /// /// This contains tables to intern any component types found as well as diff --git a/crates/environ/src/component/vmcomponent_offsets.rs b/crates/environ/src/component/vmcomponent_offsets.rs new file mode 100644 index 000000000000..7701ae3781bf --- /dev/null +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -0,0 +1,118 @@ +// Currently the `VMComponentContext` allocation by field looks like this: +// +// struct VMComponentContext { +// may_enter: u8, +// may_leave: u8, +// lowering_callees: [VMLoweringCallee; component.num_lowerings], +// } + +use crate::component::{Component, LoweredIndex}; +use crate::PtrSize; + +/// 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 in this component + pub num_lowerings: u32, + + // precalculated offsets of various member fields + may_enter: u32, + may_leave: u32, + lowering_callees: 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.lowerings.len().try_into().unwrap(), + may_enter: 0, + may_leave: 0, + lowering_callees: 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(may_enter) = 1u32, + size(may_leave) = 1u32, + align(u32::from(ret.ptr.size())), + size(lowering_callees) = cmul(ret.num_lowerings, ret.ptr.size()), + } + + ret.size = next_field_offset; + + return ret; + } + + /// The size, in bytes, of the host pointer. + #[inline] + pub fn pointer_size(&self) -> u8 { + self.ptr.size() + } + + /// 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 `lowering_callees` field. + #[inline] + pub fn lowering_callees(&self) -> u32 { + self.lowering_callees + } + + /// The offset of the callee for the `index` specified. + #[inline] + pub fn lowering_callee(&self, index: LoweredIndex) -> u32 { + assert!(index.as_u32() < self.num_lowerings); + self.lowering_callees() + 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/runtime/Cargo.toml b/crates/runtime/Cargo.toml index bb1c3373770c..56bcd62f71b2 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -54,3 +54,5 @@ pooling-allocator = [] # It is useful for applications that do not bind their own exception ports and # need portable signal handling. posix-signals-on-macos = [] + +component-model = ["wasmtime-environ/component-model"] diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs new file mode 100644 index 000000000000..f80e7bbf67f1 --- /dev/null +++ b/crates/runtime/src/component.rs @@ -0,0 +1,138 @@ +//! TODO + +use crate::ValRaw; +use std::alloc::{self, Layout}; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::ptr; +use wasmtime_environ::component::{Component, LoweredIndex, VMComponentOffsets}; +use wasmtime_environ::{HostPtr, PrimaryMap}; + +/// TODO +#[repr(C)] +pub struct ComponentInstance { + offsets: VMComponentOffsets, + vmctx: VMComponentContext, +} + +/// TODO +pub type VMLoweringCallee = extern "C" fn(*mut VMComponentContext, *mut ValRaw, usize); + +/// TODO +#[repr(C)] +#[cfg_attr(target_pointer_width = "32", repr(align(4)))] +#[cfg_attr(target_pointer_width = "64", repr(align(8)))] +pub struct VMComponentContext {} + +impl ComponentInstance { + fn alloc_layout(offsets: &VMComponentOffsets) -> Layout { + let size = mem::size_of::() + .checked_add(usize::try_from(offsets.size_of_vmctx()).unwrap()) + .unwrap(); + let align = mem::align_of::(); + Layout::from_size_align(size, align).unwrap() + } + + unsafe fn new_at( + ptr: *mut ComponentInstance, + alloc_size: usize, + offsets: VMComponentOffsets, + lowerings: &PrimaryMap, + ) { + assert!(alloc_size >= Self::alloc_layout(&offsets).size()); + + ptr::write( + ptr, + ComponentInstance { + offsets, + vmctx: VMComponentContext {}, + }, + ); + + (*ptr).initialize_vmctx(lowerings); + } + + fn vmctx(&self) -> *mut VMComponentContext { + &self.vmctx as *const VMComponentContext as *mut VMComponentContext + } + + unsafe fn vmctx_plus_offset(&self, offset: u32) -> *mut T { + self.vmctx() + .cast::() + .add(usize::try_from(offset).unwrap()) + .cast() + } + + /// TODO + pub fn may_leave(&self) -> *mut bool { + unsafe { self.vmctx_plus_offset(self.offsets.may_leave()) } + } + + /// TODO + pub fn may_enter(&self) -> *mut bool { + unsafe { self.vmctx_plus_offset(self.offsets.may_enter()) } + } + + unsafe fn initialize_vmctx(&mut self, lowerings: &PrimaryMap) { + *self.may_leave() = true; + *self.may_enter() = true; + + debug_assert_eq!(lowerings.len(), self.offsets.num_lowerings as usize); + ptr::copy_nonoverlapping( + lowerings.values().as_slice().as_ptr(), + self.vmctx_plus_offset(self.offsets.lowering_callees()), + lowerings.len(), + ); + } +} + +/// TODO +pub struct OwnedComponentInstance { + ptr: ptr::NonNull, +} + +// TODO +unsafe impl Send for OwnedComponentInstance where ComponentInstance: Send {} +unsafe impl Sync for OwnedComponentInstance where ComponentInstance: Sync {} + +impl OwnedComponentInstance { + /// TODO + pub fn new( + component: &Component, + lowerings: &PrimaryMap, + ) -> OwnedComponentInstance { + let offsets = VMComponentOffsets::new(HostPtr, component); + let layout = ComponentInstance::alloc_layout(&offsets); + unsafe { + let ptr = alloc::alloc(layout) as *mut ComponentInstance; + let ptr = ptr::NonNull::new(ptr).unwrap(); + + ComponentInstance::new_at(ptr.as_ptr(), layout.size(), offsets, lowerings); + + OwnedComponentInstance { ptr } + } + } +} + +impl Deref for OwnedComponentInstance { + type Target = ComponentInstance; + fn deref(&self) -> &ComponentInstance { + unsafe { &*self.ptr.as_ptr() } + } +} + +impl DerefMut for OwnedComponentInstance { + fn deref_mut(&mut self) -> &mut ComponentInstance { + unsafe { &mut *self.ptr.as_ptr() } + } +} + +impl Drop for OwnedComponentInstance { + fn drop(&mut self) { + let layout = ComponentInstance::alloc_layout(&self.offsets); + unsafe { + ptr::drop_in_place(self.ptr.as_ptr()); + alloc::dealloc(self.ptr.as_ptr().cast(), layout); + } + } +} diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index d7d7d0ec92e2..7918039f963f 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -30,6 +30,8 @@ use wasmtime_environ::DefinedMemoryIndex; use wasmtime_environ::FunctionInfo; use wasmtime_environ::SignatureIndex; +#[cfg(feature = "component-model")] +pub mod component; mod export; mod externref; mod imports; diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 4e78397dcb26..8b9f3d542b15 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -108,4 +108,8 @@ 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", + "wasmtime-runtime/component-model", +] diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index f0d7941d4b94..d220cb5b0b73 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -1,10 +1,15 @@ use crate::{Engine, Module}; use anyhow::{bail, Context, Result}; use std::fs; +use std::ops::Range; use std::path::Path; use std::sync::Arc; -use wasmtime_environ::component::{ComponentTypes, ModuleUpvarIndex, Translation, Translator}; +use wasmtime_environ::component::{ + ComponentTypes, LoweredIndex, ModuleUpvarIndex, TrampolineInfo, Translation, Translator, +}; use wasmtime_environ::PrimaryMap; +use wasmtime_jit::CodeMemory; +use wasmtime_runtime::VMFunctionBody; /// A compiled WebAssembly Component. // @@ -18,6 +23,9 @@ struct ComponentInner { component: wasmtime_environ::component::Component, upvars: PrimaryMap, types: Arc, + trampolines: PrimaryMap, + trampoline_obj: CodeMemory, + text: Range, } impl Component { @@ -84,26 +92,60 @@ 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 indices = component + .lowerings + .iter() + .map(|(i, _)| i) + .collect::>(); + let compiler = engine.compiler().component_compiler(); + let trampolines = engine + .run_maybe_parallel(indices, |index| { + compiler.compile_lowered_trampoline(&component, index, &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); Ok(Component { inner: Arc::new(ComponentInner { component, upvars, types, + trampolines, + trampoline_obj, + text, }), }) } @@ -119,4 +161,11 @@ impl Component { pub(crate) fn types(&self) -> &Arc { &self.inner.types } + + pub(crate) fn trampoline_ptr(&self, index: LoweredIndex) -> *const VMFunctionBody { + 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]; + trampoline.as_ptr().cast() + } } diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index 151c5d128e6f..a1b52dac1821 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -10,7 +10,9 @@ use wasmtime_environ::component::{ use wasmtime_environ::PrimaryMap; use wasmtime_runtime::{Export, ExportFunction, ExportMemory, VMTrampoline}; +mod host; mod typed; +pub use self::host::*; pub use self::typed::*; /// A WebAssembly component function. @@ -47,40 +49,41 @@ impl Func { instances: &PrimaryMap, func: &LiftedFunction, ) -> Func { - let export = match lookup(store, instances, &func.func) { - Export::Function(f) => f, - _ => unreachable!(), - }; - let trampoline = store.lookup_trampoline(unsafe { export.anyfunc.as_ref() }); - let intrinsics = func.options.intrinsics.as_ref().map(|i| { - let memory = match lookup(store, instances, &i.memory) { - Export::Memory(m) => m, - _ => unreachable!(), - }; - let realloc = match lookup(store, instances, &i.canonical_abi_realloc) { - Export::Function(f) => f, - _ => unreachable!(), - }; - let free = match lookup(store, instances, &i.canonical_abi_free) { - Export::Function(f) => f, - _ => unreachable!(), - }; - Intrinsics { - memory, - realloc, - free, - } - }); - Func(store.store_data_mut().insert(FuncData { - trampoline, - export, - options: Options { - intrinsics, - string_encoding: func.options.string_encoding, - }, - ty: func.ty, - types: types.clone(), - })) + panic!() + // let export = match lookup(store, instances, &func.func) { + // Export::Function(f) => f, + // _ => unreachable!(), + // }; + // let trampoline = store.lookup_trampoline(unsafe { export.anyfunc.as_ref() }); + // let intrinsics = func.options.intrinsics.as_ref().map(|i| { + // let memory = match lookup(store, instances, &i.memory) { + // Export::Memory(m) => m, + // _ => unreachable!(), + // }; + // let realloc = match lookup(store, instances, &i.canonical_abi_realloc) { + // Export::Function(f) => f, + // _ => unreachable!(), + // }; + // let free = match lookup(store, instances, &i.canonical_abi_free) { + // Export::Function(f) => f, + // _ => unreachable!(), + // }; + // Intrinsics { + // memory, + // realloc, + // free, + // } + // }); + // Func(store.store_data_mut().insert(FuncData { + // trampoline, + // export, + // options: Options { + // intrinsics, + // string_encoding: func.options.string_encoding, + // }, + // ty: func.ty, + // types: types.clone(), + // })) } /// Attempt to cast this [`Func`] to a statically typed [`TypedFunc`] with diff --git a/crates/wasmtime/src/component/func/host.rs b/crates/wasmtime/src/component/func/host.rs new file mode 100644 index 000000000000..2b883d8b7567 --- /dev/null +++ b/crates/wasmtime/src/component/func/host.rs @@ -0,0 +1,199 @@ +use crate::component::{ComponentParams, ComponentReturn}; +use crate::{StoreContextMut, ValRaw}; + +/// TODO +pub trait IntoComponentFunc { + #[doc(hidden)] + extern "C" fn entrypoint(cx: *mut (), data: *const Self, storage: *mut ValRaw); +} + +pub trait HostComponentReturn { + // ... +} + +macro_rules! impl_into_component_func { + ($num:tt $($args:ident)*) => { + // Implement for functions without a leading `StoreContextMut` + // parameter, delegating to the implementation below which does have the + // leading `StoreContextMut` parameter. + #[allow(non_snake_case)] + impl IntoComponentFunc for F + where + F: Fn($($args),*) -> R + Send + Sync + 'static, + ($($args,)*): ComponentParams, + R: HostComponentReturn, + { + extern "C" fn entrypoint(cx: *mut (), data: *const Self, storage: *mut ValRaw) { + let data = data as usize; + let f = move |_: StoreContextMut<'_, T>, $($args:$args),*| { + unsafe { (*(data as *const Self))($($args),*) } + }; + IntoComponentFunc::entrypoint(cx, &f, storage) + } + } + + #[allow(non_snake_case)] + impl IntoComponentFunc, $($args,)*), R> for F + where + F: Fn(StoreContextMut<'_, T>, $($args),*) -> R + Send + Sync + 'static, + ($($args,)*): ComponentParams, + R: HostComponentReturn, + { + extern "C" fn entrypoint(cx: *mut (), data: *const Self, storage: *mut ValRaw) { + panic!() + } + //fn into_func(self, engine: &Engine) -> (InstanceHandle, VMTrampoline) { + // /// This shim is called by Wasm code, constructs a `Caller`, + // /// calls the wrapped host function, and returns the translated + // /// result back to Wasm. + // /// + // /// Note that this shim's ABI must *exactly* match that expected + // /// by Cranelift, since Cranelift is generating raw function + // /// calls directly to this function. + // unsafe extern "C" fn wasm_to_host_shim( + // vmctx: *mut VMContext, + // caller_vmctx: *mut VMContext, + // $( $args: $args::Abi, )* + // retptr: R::Retptr, + // ) -> R::Abi + // where + // F: Fn(Caller<'_, T>, $( $args ),*) -> R + 'static, + // $( $args: WasmTy, )* + // R: WasmRet, + // { + // enum CallResult { + // Ok(U), + // Trap(anyhow::Error), + // Panic(Box), + // } + + // // Note that this `result` is intentionally scoped into a + // // separate block. Handling traps and panics will involve + // // longjmp-ing from this function which means we won't run + // // destructors. As a result anything requiring a destructor + // // should be part of this block, and the long-jmp-ing + // // happens after the block in handling `CallResult`. + // let result = Caller::with(caller_vmctx, |mut caller| { + // let state = (*vmctx).host_state(); + // // Double-check ourselves in debug mode, but we control + // // the `Any` here so an unsafe downcast should also + // // work. + // debug_assert!(state.is::()); + // let func = &*(state as *const _ as *const F); + + // let ret = { + // panic::catch_unwind(AssertUnwindSafe(|| { + // if let Err(trap) = caller.store.0.call_hook(CallHook::CallingHost) { + // return R::fallible_from_trap(trap); + // } + // $(let $args = $args::from_abi($args, caller.store.0);)* + // let r = func( + // caller.sub_caller(), + // $( $args, )* + // ); + // if let Err(trap) = caller.store.0.call_hook(CallHook::ReturningFromHost) { + // return R::fallible_from_trap(trap); + // } + // r.into_fallible() + // })) + // }; + + // // Note that we need to be careful when dealing with traps + // // here. Traps are implemented with longjmp/setjmp meaning + // // that it's not unwinding and consequently no Rust + // // destructors are run. We need to be careful to ensure that + // // nothing on the stack needs a destructor when we exit + // // abnormally from this `match`, e.g. on `Err`, on + // // cross-store-issues, or if `Ok(Err)` is raised. + // match ret { + // Err(panic) => CallResult::Panic(panic), + // Ok(ret) => { + // // Because the wrapped function is not `unsafe`, we + // // can't assume it returned a value that is + // // compatible with this store. + // if !ret.compatible_with_store(caller.store.0) { + // CallResult::Trap(anyhow::anyhow!("host function attempted to return cross-`Store` value to Wasm")) + // } else { + // match ret.into_abi_for_ret(caller.store.0, retptr) { + // Ok(val) => CallResult::Ok(val), + // Err(trap) => CallResult::Trap(trap.into()), + // } + // } + + // } + // } + // }); + + // match result { + // CallResult::Ok(val) => val, + // CallResult::Trap(trap) => raise_user_trap(trap), + // CallResult::Panic(panic) => wasmtime_runtime::resume_panic(panic), + // } + // } + + // /// This trampoline allows host code to indirectly call the + // /// wrapped function (e.g. via `Func::call` on a `funcref` that + // /// happens to reference our wrapped function). + // /// + // /// It reads the arguments out of the incoming `args` array, + // /// calls the given function pointer, and then stores the result + // /// back into the `args` array. + // unsafe extern "C" fn host_trampoline<$($args,)* R>( + // callee_vmctx: *mut VMContext, + // caller_vmctx: *mut VMContext, + // ptr: *const VMFunctionBody, + // args: *mut ValRaw, + // ) + // where + // $($args: WasmTy,)* + // R: WasmRet, + // { + // let ptr = mem::transmute::< + // *const VMFunctionBody, + // unsafe extern "C" fn( + // *mut VMContext, + // *mut VMContext, + // $( $args::Abi, )* + // R::Retptr, + // ) -> R::Abi, + // >(ptr); + + // let mut _n = 0; + // $( + // let $args = $args::abi_from_raw(args.add(_n)); + // _n += 1; + // )* + // R::wrap_trampoline(args, |retptr| { + // ptr(callee_vmctx, caller_vmctx, $( $args, )* retptr) + // }); + // } + + // let ty = R::func_type( + // None::.into_iter() + // $(.chain(Some($args::valtype())))* + // ); + + // let shared_signature_id = engine.signatures().register(ty.as_wasm_func_type()); + + // let trampoline = host_trampoline::<$($args,)* R>; + + + // let instance = unsafe { + // crate::trampoline::create_raw_function( + // std::slice::from_raw_parts_mut( + // wasm_to_host_shim:: as *mut _, + // 0, + // ), + // shared_signature_id, + // Box::new(self), + // ) + // .expect("failed to create raw function") + // }; + + // (instance, trampoline) + //} + } + } +} + +for_each_function_signature!(impl_into_component_func); diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index ab5e53325f1f..5ce236853d56 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -4,9 +4,10 @@ use crate::store::{StoreOpaque, Stored}; use crate::{AsContextMut, Module, StoreContextMut}; use anyhow::{anyhow, Context, Result}; use wasmtime_environ::component::{ - CoreExport, Export, ExportItem, Instantiation, RuntimeInstanceIndex, + CoreDef, CoreExport, Export, ExportItem, Instantiation, RuntimeInstanceIndex, }; use wasmtime_environ::{EntityIndex, PrimaryMap}; +use wasmtime_runtime::component::OwnedComponentInstance; /// An instantiated component. /// @@ -24,6 +25,7 @@ pub(crate) struct InstanceData { // alive and things like that, instead only the bare minimum necessary // should be kept alive here (mostly just `wasmtime_environ::Component`. component: Component, + state: OwnedComponentInstance, } impl Instance { @@ -38,11 +40,12 @@ impl Instance { let mut instantiator = Instantiator::new(component); instantiator.run(&mut store)?; - let data = Box::new(InstanceData { - instances: instantiator.instances, - component: component.clone(), - }); - Ok(Instance(store.0.store_data_mut().insert(Some(data)))) + panic!() + // let data = Box::new(InstanceData { + // instances: instantiator.instances, + // component: component.clone(), + // }); + // Ok(Instance(store.0.store_data_mut().insert(Some(data)))) } /// Looks up a function by name within this [`Instance`]. @@ -109,6 +112,7 @@ impl InstanceData { &self.instances, func, )), + Export::Reexport { .. } => unimplemented!("component reexports"), } } } @@ -139,8 +143,8 @@ impl<'a> Instantiator<'a> { Instantiation::ModuleUpvar { module, args } => { (self.component.upvar(*module), args) } - Instantiation::ModuleImport { import_index, args } => { - drop((import_index, args)); + Instantiation::ModuleImport { import, args } => { + drop((import, args)); unimplemented!("component module imports"); } }; @@ -162,7 +166,7 @@ impl<'a> Instantiator<'a> { &mut self, store: &mut StoreOpaque, module: &Module, - args: &[CoreExport], + args: &[CoreDef], ) -> &OwnedImports { self.imports.clear(); self.imports.reserve(module); @@ -182,20 +186,21 @@ impl<'a> Instantiator<'a> { } } -pub(crate) fn lookup( +pub(crate) fn lookup( store: &mut StoreOpaque, instances: &PrimaryMap, - item: &CoreExport, + item: &CoreDef, ) -> wasmtime_runtime::Export -where - T: Copy + Into, +// where +// T: Copy + Into, { - let instance = &instances[item.instance]; - let id = instance.id(store); - let instance = store.instance_mut(id); - let idx = match &item.item { - ExportItem::Index(idx) => (*idx).into(), - ExportItem::Name(name) => instance.module().exports[name], - }; - instance.get_export_by_index(idx) + panic!() + // let instance = &instances[item.instance]; + // let id = instance.id(store); + // let instance = store.instance_mut(id); + // let idx = match &item.item { + // ExportItem::Index(idx) => (*idx).into(), + // ExportItem::Name(name) => instance.module().exports[name], + // }; + // instance.get_export_by_index(idx) } diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs new file mode 100644 index 000000000000..c4a84fe5b38c --- /dev/null +++ b/crates/wasmtime/src/component/linker.rs @@ -0,0 +1,150 @@ +// use crate::func::HostFunc; +// use crate::instance::InstancePre; +// use crate::store::StoreOpaque; +// use crate::{ +// AsContextMut, Caller, Engine, Extern, Func, FuncType, ImportType, Instance, IntoFunc, Module, +// StoreContextMut, Trap, Val, ValRaw, +// }; +use crate::component::{Component, Instance, IntoComponentFunc}; +use crate::{Engine, Module}; +use anyhow::{anyhow, bail, Context, Result}; +use log::warn; +use std::collections::hash_map::{Entry, HashMap}; +use std::marker; +use std::sync::Arc; + +/// TODO +pub struct Linker { + engine: Engine, + strings: Strings, + map: NameMap, + _marker: marker::PhantomData T>, +} + +#[derive(Default)] +struct Strings { + string2idx: HashMap, usize>, + strings: Vec>, +} + +/// TODO +pub struct LinkerInstance<'a, T> { + strings: &'a mut Strings, + map: &'a mut NameMap, + _marker: marker::PhantomData T>, +} + +type NameMap = HashMap; + +#[derive(Clone)] +pub(crate) enum Definition { + Instance(NameMap), + Module(Module), + // HostFunc(Arc), +} + +impl Linker { + /// TODO + pub fn new(engine: &Engine) -> Linker { + Linker { + engine: engine.clone(), + strings: Strings::default(), + map: NameMap::default(), + _marker: marker::PhantomData, + } + } + + /// Returns the [`Engine`] this is connected to. + pub fn engine(&self) -> &Engine { + &self.engine + } + + /// TODO + pub fn root(&mut self) -> LinkerInstance<'_, T> { + LinkerInstance { + strings: &mut self.strings, + map: &mut self.map, + _marker: self._marker, + } + } + + /// TODO + pub fn instance(&mut self, name: &str) -> Result> { + self.root().into_instance(name) + } + + /// TODO + pub fn instantiate(&self, component: &Component) -> Result { + panic!() + } +} + +impl LinkerInstance<'_, T> { + fn as_mut(&mut self) -> LinkerInstance<'_, T> { + LinkerInstance { + strings: self.strings, + map: self.map, + _marker: self._marker, + } + } + + /// TODO + pub fn func_wrap( + &mut self, + name: &str, + func: impl IntoComponentFunc, + ) -> Result<()> { + panic!() + } + + /// TODO + pub fn module(&mut self, name: &str, module: &Module) -> Result<()> { + let name = self.strings.intern(name); + self.insert(name, Definition::Module(module.clone())) + } + + /// TODO + pub fn instance(&mut self, name: &str) -> Result> { + self.as_mut().into_instance(name) + } + + /// TODO + pub fn into_instance(mut self, name: &str) -> Result { + let name = self.strings.intern(name); + let slot = match self.map.entry(name) { + Entry::Occupied(_) => { + bail!("import of `{}` defined twice", self.strings.strings[name]) + } + Entry::Vacant(v) => v, + }; + self.map = match slot.insert(Definition::Instance(NameMap::default())) { + Definition::Instance(map) => map, + _ => unreachable!(), + }; + Ok(self) + } + + fn insert(&mut self, key: usize, item: Definition) -> Result<()> { + let slot = match self.map.entry(key) { + Entry::Occupied(_) => { + bail!("import of `{}` defined twice", self.strings.strings[key]) + } + Entry::Vacant(v) => v, + }; + slot.insert(item); + Ok(()) + } +} + +impl Strings { + fn intern(&mut self, string: &str) -> usize { + if let Some(idx) = self.string2idx.get(string) { + return *idx; + } + let string: Arc = string.into(); + let idx = self.strings.len(); + self.strings.push(string.clone()); + self.string2idx.insert(string, idx); + idx + } +} diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs index 99c7da84b723..199f8f11d48e 100644 --- a/crates/wasmtime/src/component/mod.rs +++ b/crates/wasmtime/src/component/mod.rs @@ -6,12 +6,15 @@ mod component; mod func; mod instance; +mod linker; mod store; pub use self::component::Component; pub use self::func::{ - ComponentParams, ComponentReturn, ComponentValue, Cursor, Func, TypedFunc, Value, + ComponentParams, ComponentReturn, ComponentValue, Cursor, Func, IntoComponentFunc, TypedFunc, + Value, }; pub use self::instance::Instance; +pub use self::linker::Linker; // These items are expected to be used by an eventual // `#[derive(ComponentValue)]`, they are not part of Wasmtime's API stability diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index 4886d7891075..cd8df510434d 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -224,6 +224,22 @@ impl Engine { .collect::, E>>() } + 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 {