diff --git a/crates/c-api/src/component/types/val.rs b/crates/c-api/src/component/types/val.rs index b705ada33031..310a453cebd6 100644 --- a/crates/c-api/src/component/types/val.rs +++ b/crates/c-api/src/component/types/val.rs @@ -31,6 +31,7 @@ pub enum wasmtime_component_valtype_t { Future(Box), Stream(Box), ErrorContext, + FixedSizeList(Box), } impl From for wasmtime_component_valtype_t { @@ -61,6 +62,7 @@ impl From for wasmtime_component_valtype_t { Type::Borrow(ty) => Self::Borrow(Box::new(ty.into())), Type::Future(ty) => Self::Future(Box::new(ty.into())), Type::Stream(ty) => Self::Stream(Box::new(ty.into())), + Type::FixedSizeList(ty) => Self::FixedSizeList(Box::new(ty.into())), Type::ErrorContext => Self::ErrorContext, } } diff --git a/crates/c-api/src/component/val.rs b/crates/c-api/src/component/val.rs index 00c2c6a63963..59cd952e7b77 100644 --- a/crates/c-api/src/component/val.rs +++ b/crates/c-api/src/component/val.rs @@ -234,6 +234,7 @@ pub enum wasmtime_component_val_t { Result(wasmtime_component_valresult_t), Flags(wasmtime_component_valflags_t), Resource(Box), + FixedSizeList(wasmtime_component_vallist_t), } impl Default for wasmtime_component_val_t { @@ -276,6 +277,7 @@ impl From<&wasmtime_component_val_t> for Val { wasmtime_component_val_t::Result(x) => Val::Result(x.into()), wasmtime_component_val_t::Flags(x) => Val::Flags(x.into()), wasmtime_component_val_t::Resource(x) => Val::Resource(x.resource), + wasmtime_component_val_t::FixedSizeList(x) => Val::FixedSizeList(x.into()), } } } @@ -317,6 +319,9 @@ impl From<&Val> for wasmtime_component_val_t { Val::Future(_) => todo!(), Val::Stream(_) => todo!(), Val::ErrorContext(_) => todo!(), + Val::FixedSizeList(ty) => wasmtime_component_val_t::FixedSizeList( + wasmtime_component_vallist_t::from(ty.as_ref()), + ), } } } diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 51fbd9b1ea63..b7ca5b02bb87 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -409,6 +409,9 @@ wasmtime_option_group! { pub exceptions: Option, /// Whether or not any GC infrastructure in Wasmtime is enabled or not. pub gc_support: Option, + /// Component model support for fixed size lists: this corresponds + /// to the 🔧 emoji in the component model specification + pub component_model_fixed_size_list: Option, } enum Wasm { @@ -1067,6 +1070,7 @@ impl CommonOptions { ("component-model-async", component_model_async_stackful, wasm_component_model_async_stackful) ("component-model-async", component_model_threading, wasm_component_model_threading) ("component-model", component_model_error_context, wasm_component_model_error_context) + ("component-model", component_model_fixed_size_list, wasm_component_model_fixed_size_lists) ("threads", threads, wasm_threads) ("gc", gc, wasm_gc) ("gc", reference_types, wasm_reference_types) diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 8db6b800c4fe..d14635da4e72 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -89,6 +89,8 @@ indices! { pub struct TypeResultIndex(u32); /// Index pointing to a list type in the component model. pub struct TypeListIndex(u32); + /// Index pointing to a fixed size list type in the component model. + pub struct TypeFixedSizeListIndex(u32); /// Index pointing to a future type in the component model. pub struct TypeFutureIndex(u32); @@ -296,6 +298,7 @@ pub struct ComponentTypes { pub(super) stream_tables: PrimaryMap, pub(super) error_context_tables: PrimaryMap, + pub(super) fixed_size_lists: PrimaryMap, } impl TypeTrace for ComponentTypes { @@ -369,6 +372,7 @@ impl ComponentTypes { InterfaceType::Enum(i) => &self[*i].abi, InterfaceType::Option(i) => &self[*i].abi, InterfaceType::Result(i) => &self[*i].abi, + InterfaceType::FixedSizeList(i) => &self[*i].abi, } } @@ -418,6 +422,7 @@ impl_index! { impl Index for ComponentTypes { TypeFutureTable => future_tables } impl Index for ComponentTypes { TypeStreamTable => stream_tables } impl Index for ComponentTypes { TypeErrorContextTable => error_context_tables } + impl Index for ComponentTypes { TypeFixedSizeList => fixed_size_lists } } // Additionally forward anything that can index `ModuleTypes` to `ModuleTypes` @@ -595,6 +600,7 @@ pub enum InterfaceType { Future(TypeFutureTableIndex), Stream(TypeStreamTableIndex), ErrorContext(TypeComponentLocalErrorContextTableIndex), + FixedSizeList(TypeFixedSizeListIndex), } /// Bye information about a type in the canonical ABI, with metadata for both @@ -716,6 +722,23 @@ impl CanonicalAbiInfo { return ret; } + /// Returns the abi for a fixed size list + pub const fn fixed_size_list_static( + element: &CanonicalAbiInfo, + count: u32, + ) -> CanonicalAbiInfo { + CanonicalAbiInfo { + size32: element.size32 * count, + align32: element.align32, + size64: element.size64 * count, + align64: element.align64, + flat_count: match element.flat_count { + None => None, + Some(c) => Some(c.saturating_mul(if count > 255 { 255u8 } else { count as u8 })), + }, + } + } + /// Returns the delta from the current value of `offset` to align properly /// and read the next record field of type `abi` for 32-bit memories. pub fn next_field32(&self, offset: &mut u32) -> u32 { @@ -1175,6 +1198,17 @@ pub struct TypeList { pub element: InterfaceType, } +/// Shape of a "fixed size list" interface type. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeFixedSizeList { + /// The element type of the list. + pub element: InterfaceType, + /// The fixed length of the list. + pub size: u32, + /// Byte information about this type in the canonical ABI. + pub abi: CanonicalAbiInfo, +} + /// Maximum number of flat types, for either params or results. pub const MAX_FLAT_TYPES: usize = if MAX_FLAT_PARAMS > MAX_FLAT_RESULTS { MAX_FLAT_PARAMS diff --git a/crates/environ/src/component/types_builder.rs b/crates/environ/src/component/types_builder.rs index 090081388a2b..d3ddcbc4373a 100644 --- a/crates/environ/src/component/types_builder.rs +++ b/crates/environ/src/component/types_builder.rs @@ -50,6 +50,7 @@ pub struct ComponentTypesBuilder { future_tables: HashMap, stream_tables: HashMap, error_context_tables: HashMap, + fixed_size_lists: HashMap, component_types: ComponentTypes, module_types: ModuleTypesBuilder, @@ -118,6 +119,7 @@ impl ComponentTypesBuilder { type_info: TypeInformationCache::default(), resources: ResourcesBuilder::default(), abstract_resources: 0, + fixed_size_lists: HashMap::default(), } } @@ -456,8 +458,8 @@ impl ComponentTypesBuilder { ComponentDefinedType::Stream(ty) => { InterfaceType::Stream(self.stream_table_type(types, ty)?) } - ComponentDefinedType::FixedSizeList(..) => { - bail!("support not implemented for fixed-size-lists"); + ComponentDefinedType::FixedSizeList(ty, size) => { + InterfaceType::FixedSizeList(self.fixed_size_list_type(types, ty, *size)?) } ComponentDefinedType::Map(..) => { bail!("support not implemented for map type"); @@ -575,6 +577,33 @@ impl ComponentTypesBuilder { self.add_tuple_type(TypeTuple { types, abi }) } + fn fixed_size_list_type( + &mut self, + types: TypesRef<'_>, + ty: &ComponentValType, + size: u32, + ) -> Result { + assert_eq!(types.id(), self.module_types.validator_id()); + let element = self.valtype(types, ty)?; + Ok(self.new_fixed_size_list_type(element, size)) + } + + pub(crate) fn new_fixed_size_list_type( + &mut self, + element: InterfaceType, + size: u32, + ) -> TypeFixedSizeListIndex { + let element_abi = self.component_types.canonical_abi(&element); + let mut abi = element_abi.clone(); + // this assumes that size32 is already rounded up to alignment + abi.size32 = element_abi.size32 * size; + abi.size64 = element_abi.size64 * size; + abi.flat_count = element_abi + .flat_count + .map(|c| c.saturating_mul(size.min(255) as u8)); + self.add_fixed_size_list_type(TypeFixedSizeList { element, size, abi }) + } + fn flags_type(&mut self, flags: &IndexSet) -> TypeFlagsIndex { let flags = TypeFlags { names: flags.iter().map(|s| s.to_string()).collect(), @@ -691,6 +720,11 @@ impl ComponentTypesBuilder { intern_and_fill_flat_types!(self, tuples, ty) } + /// Interns a new tuple type within this type information. + pub fn add_fixed_size_list_type(&mut self, ty: TypeFixedSizeList) -> TypeFixedSizeListIndex { + intern_and_fill_flat_types!(self, fixed_size_lists, ty) + } + /// Interns a new variant type within this type information. pub fn add_variant_type(&mut self, ty: TypeVariant) -> TypeVariantIndex { intern_and_fill_flat_types!(self, variants, ty) @@ -826,6 +860,7 @@ impl ComponentTypesBuilder { InterfaceType::Enum(i) => &self.type_info.enums[*i], InterfaceType::Option(i) => &self.type_info.options[*i], InterfaceType::Result(i) => &self.type_info.results[*i], + InterfaceType::FixedSizeList(i) => &self.type_info.fixed_size_lists[*i], } } } @@ -935,6 +970,7 @@ struct TypeInformationCache { options: PrimaryMap, results: PrimaryMap, lists: PrimaryMap, + fixed_size_lists: PrimaryMap, } struct TypeInformation { @@ -1084,6 +1120,24 @@ impl TypeInformation { self.build_record(ty.types.iter().map(|t| types.type_information(t))); } + fn fixed_size_lists(&mut self, types: &ComponentTypesBuilder, ty: &TypeFixedSizeList) { + let element_info = types.type_information(&ty.element); + self.depth = 1 + element_info.depth; + self.has_borrow = element_info.has_borrow; + match element_info.flat.as_flat_types() { + Some(types) => { + 'outer: for _ in 0..ty.size { + for (t32, t64) in types.memory32.iter().zip(types.memory64) { + if !self.flat.push(*t32, *t64) { + break 'outer; + } + } + } + } + None => self.flat.len = u8::try_from(MAX_FLAT_TYPES + 1).unwrap(), + } + } + fn enums(&mut self, _types: &ComponentTypesBuilder, _ty: &TypeEnum) { self.depth = 1; self.flat.push(FlatType::I32, FlatType::I32); diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index f75c30ccf34a..269d632ed73d 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -19,9 +19,10 @@ use crate::component::{ CanonicalAbiInfo, ComponentTypesBuilder, FLAG_MAY_ENTER, FLAG_MAY_LEAVE, FixedEncoding as FE, FlatType, InterfaceType, MAX_FLAT_ASYNC_PARAMS, MAX_FLAT_PARAMS, PREPARE_ASYNC_NO_RESULT, PREPARE_ASYNC_WITH_RESULT, START_FLAG_ASYNC_CALLEE, StringEncoding, Transcode, - TypeComponentLocalErrorContextTableIndex, TypeEnumIndex, TypeFlagsIndex, TypeFutureTableIndex, - TypeListIndex, TypeOptionIndex, TypeRecordIndex, TypeResourceTableIndex, TypeResultIndex, - TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex, VariantInfo, + TypeComponentLocalErrorContextTableIndex, TypeEnumIndex, TypeFixedSizeListIndex, + TypeFlagsIndex, TypeFutureTableIndex, TypeListIndex, TypeOptionIndex, TypeRecordIndex, + TypeResourceTableIndex, TypeResultIndex, TypeStreamTableIndex, TypeTupleIndex, + TypeVariantIndex, VariantInfo, }; use crate::fact::signature::Signature; use crate::fact::transcode::Transcoder; @@ -1126,6 +1127,7 @@ impl<'a, 'b> Compiler<'a, 'b> { | InterfaceType::Future(_) | InterfaceType::Stream(_) | InterfaceType::ErrorContext(_) => 1, + InterfaceType::FixedSizeList(i) => self.types[*i].size as usize, }; match self.fuel.checked_sub(cost) { @@ -1165,6 +1167,9 @@ impl<'a, 'b> Compiler<'a, 'b> { InterfaceType::ErrorContext(t) => { self.translate_error_context(*t, src, dst_ty, dst) } + InterfaceType::FixedSizeList(t) => { + self.translate_fixed_size_list(*t, src, dst_ty, dst); + } } } @@ -2858,6 +2863,113 @@ impl<'a, 'b> Compiler<'a, 'b> { } } + fn translate_fixed_size_list( + &mut self, + src_ty: TypeFixedSizeListIndex, + src: &Source<'_>, + dst_ty: &InterfaceType, + dst: &Destination, + ) { + let src_ty = &self.types[src_ty]; + let dst_ty = match dst_ty { + InterfaceType::FixedSizeList(t) => &self.types[*t], + _ => panic!("expected a fixed size list"), + }; + + // TODO: subtyping + assert_eq!(src_ty.size, dst_ty.size); + + match (&src, &dst) { + // Generate custom code for memory to memory copy + (Source::Memory(src_mem), Destination::Memory(dst_mem)) => { + let src_mem_opts = match &src_mem.opts.data_model { + DataModel::Gc {} => todo!("CM+GC"), + DataModel::LinearMemory(opts) => opts, + }; + let dst_mem_opts = match &dst_mem.opts.data_model { + DataModel::Gc {} => todo!("CM+GC"), + DataModel::LinearMemory(opts) => opts, + }; + let src_element_bytes = self.types.size_align(src_mem_opts, &src_ty.element).0; + let dst_element_bytes = self.types.size_align(dst_mem_opts, &dst_ty.element).0; + assert_ne!(src_element_bytes, 0); + assert_ne!(dst_element_bytes, 0); + + // because data is stored in-line, we assume that source and destination memory have been validated upstream + + self.instruction(LocalGet(src_mem.addr.idx)); + if src_mem.offset != 0 { + self.ptr_uconst(src_mem_opts, src_mem.offset); + self.ptr_add(src_mem_opts); + } + let cur_src_ptr = self.local_set_new_tmp(src_mem_opts.ptr()); + self.instruction(LocalGet(dst_mem.addr.idx)); + if dst_mem.offset != 0 { + self.ptr_uconst(dst_mem_opts, dst_mem.offset); + self.ptr_add(dst_mem_opts); + } + let cur_dst_ptr = self.local_set_new_tmp(dst_mem_opts.ptr()); + + self.instruction(I32Const(src_ty.size as i32)); + let remaining = self.local_set_new_tmp(ValType::I32); + + self.instruction(Loop(BlockType::Empty)); + + // Translate the next element in the list + let element_src = Source::Memory(Memory { + opts: src_mem.opts, + offset: 0, + addr: TempLocal::new(cur_src_ptr.idx, cur_src_ptr.ty), + }); + let element_dst = Destination::Memory(Memory { + opts: dst_mem.opts, + offset: 0, + addr: TempLocal::new(cur_dst_ptr.idx, cur_dst_ptr.ty), + }); + self.translate(&src_ty.element, &element_src, &dst_ty.element, &element_dst); + + // Update the two loop pointers + self.instruction(LocalGet(cur_src_ptr.idx)); + self.ptr_uconst(src_mem_opts, src_element_bytes); + self.ptr_add(src_mem_opts); + self.instruction(LocalSet(cur_src_ptr.idx)); + self.instruction(LocalGet(cur_dst_ptr.idx)); + self.ptr_uconst(dst_mem_opts, dst_element_bytes); + self.ptr_add(dst_mem_opts); + self.instruction(LocalSet(cur_dst_ptr.idx)); + + // Update the remaining count, falling through to break out if it's zero + // now. + self.instruction(LocalGet(remaining.idx)); + self.ptr_iconst(src_mem_opts, -1); + self.ptr_add(src_mem_opts); + self.instruction(LocalTee(remaining.idx)); + self.ptr_br_if(src_mem_opts, 0); + self.instruction(End); // end of loop + + self.free_temp_local(cur_dst_ptr); + self.free_temp_local(cur_src_ptr); + self.free_temp_local(remaining); + return; + } + // for the non-memory-to-memory case fall back to using generic tuple translation + (_, _) => { + // Assumes that the number of elements are small enough for this unrolling + assert!( + src_ty.size as usize <= MAX_FLAT_PARAMS + && dst_ty.size as usize <= MAX_FLAT_PARAMS + ); + let srcs = + src.record_field_srcs(self.types, (0..src_ty.size).map(|_| src_ty.element)); + let dsts = + dst.record_field_dsts(self.types, (0..dst_ty.size).map(|_| dst_ty.element)); + for (src, dst) in srcs.zip(dsts) { + self.translate(&src_ty.element, &src, &dst_ty.element, &dst); + } + } + } + } + fn translate_variant( &mut self, src_ty: TypeVariantIndex, diff --git a/crates/fuzzing/src/oracles/component_api.rs b/crates/fuzzing/src/oracles/component_api.rs index 47f0faae57b3..57071ce1d9dc 100644 --- a/crates/fuzzing/src/oracles/component_api.rs +++ b/crates/fuzzing/src/oracles/component_api.rs @@ -117,6 +117,11 @@ fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrary::R }) .collect::>()?, ), + Type::FixedSizeList(list) => Val::FixedSizeList( + (0..list.size()) + .map(|_| arbitrary_val(&list.ty(), input)) + .collect::>()?, + ), // Resources, futures, streams, and error contexts aren't fuzzed at this time. Type::Own(_) | Type::Borrow(_) | Type::Future(_) | Type::Stream(_) | Type::ErrorContext => { diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index d4b4485d9de1..2d9b2529606a 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -1296,6 +1296,16 @@ impl Config { self } + /// This corresponds to the 🔧 emoji in the component model specification. + /// + /// Please note that Wasmtime's support for this feature is _very_ + /// incomplete. + #[cfg(feature = "component-model")] + pub fn wasm_component_model_fixed_size_lists(&mut self, enable: bool) -> &mut Self { + self.wasm_features(WasmFeatures::CM_FIXED_SIZE_LIST, enable); + self + } + /// Configures whether the [Exception-handling proposal][proposal] is enabled or not. /// /// [proposal]: https://github.com/WebAssembly/exception-handling @@ -2264,7 +2274,8 @@ impl Config { | WasmFeatures::CM_ASYNC_BUILTINS | WasmFeatures::CM_THREADING | WasmFeatures::CM_ERROR_CONTEXT - | WasmFeatures::CM_GC; + | WasmFeatures::CM_GC + | WasmFeatures::CM_FIXED_SIZE_LIST; #[allow(unused_mut, reason = "easier to avoid #[cfg]")] let mut unsupported = !features_known_to_wasmtime; diff --git a/crates/wasmtime/src/runtime/component/func/typed.rs b/crates/wasmtime/src/runtime/component/func/typed.rs index add17eddea40..0615defcf5b6 100644 --- a/crates/wasmtime/src/runtime/component/func/typed.rs +++ b/crates/wasmtime/src/runtime/component/func/typed.rs @@ -2866,6 +2866,144 @@ macro_rules! impl_component_ty_for_tuples { for_each_function_signature!(impl_component_ty_for_tuples); +unsafe impl ComponentType for [T; N] +where + T: ComponentType, +{ + type Lower = [T::Lower; N]; + + const ABI: CanonicalAbiInfo = CanonicalAbiInfo::fixed_size_list_static(&T::ABI, N as u32); + + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { + match ty { + InterfaceType::FixedSizeList(t) => T::typecheck(&types.types[*t].element, types), + other => bail!("expected `list<_, N>` found `{}`", desc(other)), + } + } +} + +unsafe impl Lower for [T; N] +where + T: Lower, +{ + fn linear_lower_to_flat( + &self, + cx: &mut LowerContext<'_, U>, + ty: InterfaceType, + dst: &mut MaybeUninit, + ) -> Result<()> { + let element = match ty { + InterfaceType::FixedSizeList(ty) => cx.types[ty].element, + _ => bad_type_info(), + }; + for (i, val) in self.iter().enumerate() { + val.linear_lower_to_flat(cx, element, map_maybe_uninit!(dst[i]))?; + } + Ok(()) + } + + fn linear_lower_to_memory( + &self, + cx: &mut LowerContext<'_, U>, + ty: InterfaceType, + offset: usize, + ) -> Result<()> { + debug_assert!(offset % (Self::ALIGN32 as usize) == 0); + let element = match ty { + InterfaceType::FixedSizeList(ty) => cx.types[ty].element, + _ => bad_type_info(), + }; + for (i, val) in self.iter().enumerate() { + val.linear_lower_to_memory(cx, element, offset + i * T::SIZE32)?; + } + Ok(()) + } +} + +unsafe impl Lift for [T; N] +where + T: Lift + Sized, +{ + fn linear_lift_from_flat( + cx: &mut LiftContext<'_>, + ty: InterfaceType, + src: &Self::Lower, + ) -> Result { + let element = match ty { + InterfaceType::FixedSizeList(ty) => cx.types[ty].element, + _ => bad_type_info(), + }; + // this is reimplementation of array::try_from_fn + let mut valid = 0; + let mut result: [MaybeUninit; N] = [const { MaybeUninit::uninit() }; N]; + let mut error: Option = None; + for n in 0..N { + match T::linear_lift_from_flat(cx, element, &src[n]) { + Ok(v) => { + result[valid].write(v); + valid += 1; + } + Err(e) => { + error = Some(e); + // continue to consume all input + } + } + } + if let Some(e) = error { + for n in 0..valid { + unsafe { + result[n].assume_init_drop(); + } + } + return Err(e); + } + assert!(valid == N); + // this is a copy of array_assume_init from stdlib to avoid requiring nightly + Ok(unsafe { (&result as *const _ as *const [T; N]).read() }) + } + + fn linear_lift_from_memory( + cx: &mut LiftContext<'_>, + ty: InterfaceType, + bytes: &[u8], + ) -> Result { + debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0); + let element = match ty { + InterfaceType::FixedSizeList(ty) => cx.types[ty].element, + _ => bad_type_info(), + }; + // this is reimplementation of array::try_from_fn + let mut valid = 0; + let mut result: [MaybeUninit; N] = [const { MaybeUninit::uninit() }; N]; + let mut error: Option = None; + let mut offset = 0; + for _ in 0..N { + match T::linear_lift_from_memory(cx, element, &bytes[offset..offset + T::SIZE32]) { + Ok(v) => { + result[valid].write(v); + valid += 1; + } + Err(e) => { + error = Some(e); + // continue to consume all input + } + } + offset += T::SIZE32; + } + if let Some(e) = error { + for n in 0..valid { + unsafe { + result[n].assume_init_drop(); + } + } + return Err(e); + } + assert!(valid == N); + // this is a copy of array_assume_init from stdlib to avoid requiring nightly + Ok(unsafe { (&result as *const _ as *const [T; N]).read() }) + } +} + pub fn desc(ty: &InterfaceType) -> &'static str { match ty { InterfaceType::U8 => "u8", @@ -2895,6 +3033,7 @@ pub fn desc(ty: &InterfaceType) -> &'static str { InterfaceType::Future(_) => "future", InterfaceType::Stream(_) => "stream", InterfaceType::ErrorContext(_) => "error-context", + InterfaceType::FixedSizeList(_) => "list<_, N>", } } diff --git a/crates/wasmtime/src/runtime/component/types.rs b/crates/wasmtime/src/runtime/component/types.rs index 948fc8e51f9d..4d1dcef2e365 100644 --- a/crates/wasmtime/src/runtime/component/types.rs +++ b/crates/wasmtime/src/runtime/component/types.rs @@ -10,10 +10,10 @@ use core::ops::Deref; use wasmtime_environ::PrimaryMap; use wasmtime_environ::component::{ ComponentTypes, Export, InterfaceType, ResourceIndex, TypeComponentIndex, - TypeComponentInstanceIndex, TypeDef, TypeEnumIndex, TypeFlagsIndex, TypeFuncIndex, - TypeFutureIndex, TypeFutureTableIndex, TypeListIndex, TypeModuleIndex, TypeOptionIndex, - TypeRecordIndex, TypeResourceTable, TypeResourceTableIndex, TypeResultIndex, TypeStreamIndex, - TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex, + TypeComponentInstanceIndex, TypeDef, TypeEnumIndex, TypeFixedSizeListIndex, TypeFlagsIndex, + TypeFuncIndex, TypeFutureIndex, TypeFutureTableIndex, TypeListIndex, TypeModuleIndex, + TypeOptionIndex, TypeRecordIndex, TypeResourceTable, TypeResourceTableIndex, TypeResultIndex, + TypeStreamIndex, TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex, }; pub use crate::component::resources::ResourceType; @@ -158,6 +158,10 @@ impl TypeChecker<'_> { (InterfaceType::Stream(_), _) => false, (InterfaceType::ErrorContext(_), InterfaceType::ErrorContext(_)) => true, (InterfaceType::ErrorContext(_), _) => false, + (InterfaceType::FixedSizeList(t1), InterfaceType::FixedSizeList(t2)) => { + self.fixed_size_lists_equal(t1, t2) + } + (InterfaceType::FixedSizeList(_), _) => false, } } @@ -167,6 +171,19 @@ impl TypeChecker<'_> { self.interface_types_equal(a.element, b.element) } + fn fixed_size_lists_equal( + &self, + l1: wasmtime_environ::component::TypeFixedSizeListIndex, + l2: wasmtime_environ::component::TypeFixedSizeListIndex, + ) -> bool { + let a = &self.a_types[l1]; + let b = &self.b_types[l2]; + if a.size != b.size { + return false; + } + self.interface_types_equal(a.element, b.element) + } + fn resources_equal(&self, o1: TypeResourceTableIndex, o2: TypeResourceTableIndex) -> bool { match (&self.a_types[o1], &self.b_types[o2]) { // Concrete resource types are the same if they map back to the @@ -324,6 +341,35 @@ impl List { } } +/// A `list` interface type +#[derive(Clone, Debug)] +pub struct FixedSizeList(Handle); + +impl PartialEq for FixedSizeList { + fn eq(&self, other: &Self) -> bool { + self.0 + .equivalent(&other.0, TypeChecker::fixed_size_lists_equal) + } +} + +impl Eq for FixedSizeList {} + +impl FixedSizeList { + pub(crate) fn from(index: TypeFixedSizeListIndex, ty: &InstanceType<'_>) -> Self { + FixedSizeList(Handle::new(index, ty)) + } + + /// Retrieve the element type of this `list`. + pub fn ty(&self) -> Type { + Type::from(&self.0.types[self.0.index].element, &self.0.instance()) + } + + /// Retrieve the size of this `list`. + pub fn size(&self) -> u32 { + self.0.types[self.0.index].size + } +} + /// A field declaration belonging to a `record` #[derive(Debug)] pub struct Field<'a> { @@ -695,6 +741,7 @@ pub enum Type { Future(FutureType), Stream(StreamType), ErrorContext, + FixedSizeList(FixedSizeList), } impl Type { @@ -855,6 +902,9 @@ impl Type { InterfaceType::Future(index) => Type::Future(instance.future_type(*index)), InterfaceType::Stream(index) => Type::Stream(instance.stream_type(*index)), InterfaceType::ErrorContext(_) => Type::ErrorContext, + InterfaceType::FixedSizeList(index) => { + Type::FixedSizeList(FixedSizeList::from(*index, instance)) + } } } @@ -886,6 +936,7 @@ impl Type { Type::Future(_) => "future", Type::Stream(_) => "stream", Type::ErrorContext => "error-context", + Type::FixedSizeList(_) => "list<_, N>", } } } diff --git a/crates/wasmtime/src/runtime/component/values.rs b/crates/wasmtime/src/runtime/component/values.rs index d0b1d0209db2..bf2efafa407a 100644 --- a/crates/wasmtime/src/runtime/component/values.rs +++ b/crates/wasmtime/src/runtime/component/values.rs @@ -90,6 +90,7 @@ pub enum Val { Future(FutureAny), Stream(StreamAny), ErrorContext(ErrorContextAny), + FixedSizeList(Vec), } impl Val { @@ -215,6 +216,11 @@ impl Val { InterfaceType::ErrorContext(_) => { ErrorContext::linear_lift_from_flat(cx, ty, next(src))?.into_val() } + InterfaceType::FixedSizeList(i) => Val::FixedSizeList( + (0..cx.types[i].size) + .map(|_| Self::lift(cx, cx.types[i].element, src)) + .collect::>()?, + ), }) } @@ -341,6 +347,20 @@ impl Val { InterfaceType::ErrorContext(_) => { ErrorContext::linear_lift_from_memory(cx, ty, bytes)?.into_val() } + InterfaceType::FixedSizeList(i) => { + let element = cx.types[i].element.clone(); + let abi = cx.types.canonical_abi(&element); + Val::Tuple( + (0..cx.types[i].size) + .map(|n| { + let offset = abi.size32 * n; + let offset = usize::try_from(offset).unwrap(); + let size = usize::try_from(abi.size32).unwrap(); + Val::load(cx, element, &bytes[offset..][..size]) + }) + .collect::>()?, + ) + } }) } @@ -491,6 +511,17 @@ impl Val { ) } (InterfaceType::ErrorContext(_), _) => unexpected(ty, self), + (InterfaceType::FixedSizeList(ty), Val::FixedSizeList(values)) => { + let ty = &cx.types[ty]; + if ty.size as usize != values.len() { + bail!("expected vec of size {}, got {}", ty.size, values.len()); + } + for value in values { + value.lower(cx, ty.element, dst)?; + } + Ok(()) + } + (InterfaceType::FixedSizeList(_), _) => unexpected(ty, self), } } @@ -644,6 +675,18 @@ impl Val { ) } (InterfaceType::ErrorContext(_), _) => unexpected(ty, self), + (InterfaceType::FixedSizeList(ty), Val::FixedSizeList(values)) => { + let ty = &cx.types[ty]; + if ty.size as usize != values.len() { + bail!("expected {} types, got {}", ty.size, values.len()); + } + let elemsize = cx.types.canonical_abi(&ty.element).size32 as usize; + for (n, value) in values.iter().enumerate() { + value.store(cx, ty.element, elemsize * n)?; + } + Ok(()) + } + (InterfaceType::FixedSizeList(_), _) => unexpected(ty, self), } } @@ -674,6 +717,7 @@ impl Val { Val::Future(_) => "future", Val::Stream(_) => "stream", Val::ErrorContext(_) => "error-context", + Val::FixedSizeList(_) => "list<_, N>", } } @@ -758,6 +802,8 @@ impl PartialEq for Val { (Self::Stream(_), _) => false, (Self::ErrorContext(l), Self::ErrorContext(r)) => l == r, (Self::ErrorContext(_), _) => false, + (Self::FixedSizeList(l), Self::FixedSizeList(r)) => l == r, + (Self::FixedSizeList(_), _) => false, } } } diff --git a/crates/wasmtime/src/runtime/wave/component.rs b/crates/wasmtime/src/runtime/wave/component.rs index 9849bb455bf6..ef4c77266da3 100644 --- a/crates/wasmtime/src/runtime/wave/component.rs +++ b/crates/wasmtime/src/runtime/wave/component.rs @@ -40,6 +40,7 @@ impl WasmType for component::Type { Self::Option(_) => WasmTypeKind::Option, Self::Result(_) => WasmTypeKind::Result, Self::Flags(_) => WasmTypeKind::Flags, + Self::FixedSizeList(_) => WasmTypeKind::FixedSizeList, Self::Own(_) | Self::Borrow(_) @@ -138,6 +139,7 @@ impl WasmValue for component::Val { Self::Option(_) => WasmTypeKind::Option, Self::Result(_) => WasmTypeKind::Result, Self::Flags(_) => WasmTypeKind::Flags, + Self::FixedSizeList(_) => WasmTypeKind::FixedSizeList, Self::Resource(_) | Self::Stream(_) | Self::Future(_) | Self::ErrorContext(_) => { WasmTypeKind::Unsupported } diff --git a/crates/wast/src/component.rs b/crates/wast/src/component.rs index 1de0bb1ba3eb..57471d9007c1 100644 --- a/crates/wast/src/component.rs +++ b/crates/wast/src/component.rs @@ -290,6 +290,7 @@ fn mismatch(expected: &ComponentConst<'_>, actual: &Val) -> Result<()> { Val::Future(..) => "future", Val::Stream(..) => "stream", Val::ErrorContext(..) => "error-context", + Val::FixedSizeList(..) => "list<_, N>", }; bail!("expected `{expected}` got `{actual}`") } diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs index 08e3730f5a12..d4150c9f4f66 100644 --- a/crates/wit-bindgen/src/lib.rs +++ b/crates/wit-bindgen/src/lib.rs @@ -1619,7 +1619,9 @@ impl<'a> InterfaceGenerator<'a> { TypeDefKind::Handle(handle) => self.type_handle(id, name, handle, &ty.docs), TypeDefKind::Resource => self.type_resource(id, name, ty, &ty.docs), TypeDefKind::Unknown => unreachable!(), - TypeDefKind::FixedSizeList(..) => todo!(), + TypeDefKind::FixedSizeList(elem, size) => { + self.type_fixed_size_list(id, name, elem, *size, &ty.docs) + } TypeDefKind::Map(..) => todo!(), } } @@ -1803,6 +1805,29 @@ impl<'a> InterfaceGenerator<'a> { } } + fn type_fixed_size_list( + &mut self, + id: TypeId, + _name: &str, + elem: &Type, + size: u32, + docs: &Docs, + ) { + let info = self.info(id); + for (name, mode) in self.modes_of(id) { + let lt = self.lifetime_for(&info, mode); + self.rustdoc(docs); + self.push_str(&format!("pub type {name}")); + self.print_generics(lt); + self.push_str(" = ["); + self.print_ty(elem, mode); + self.push_str("; "); + self.push_str(&size.to_string()); + self.push_str("];\n"); + self.assert_type(id, &name); + } + } + fn type_flags(&mut self, id: TypeId, name: &str, flags: &Flags, docs: &Docs) { self.rustdoc(docs); let wt = self.generator.wasmtime_path(); @@ -3409,7 +3434,7 @@ fn type_contains_lists(ty: Type, resolve: &Resolve) -> bool { .any(|case| option_type_contains_lists(case.ty, resolve)), TypeDefKind::Type(ty) => type_contains_lists(*ty, resolve), TypeDefKind::List(_) => true, - TypeDefKind::FixedSizeList(..) => todo!(), + TypeDefKind::FixedSizeList(elem, _) => type_contains_lists(*elem, resolve), TypeDefKind::Map(..) => todo!(), }, diff --git a/crates/wit-bindgen/src/rust.rs b/crates/wit-bindgen/src/rust.rs index bc9e39fc6770..886504ebe89f 100644 --- a/crates/wit-bindgen/src/rust.rs +++ b/crates/wit-bindgen/src/rust.rs @@ -125,14 +125,14 @@ pub trait RustGenerator<'a> { | TypeDefKind::Enum(_) | TypeDefKind::Tuple(_) | TypeDefKind::Handle(_) - | TypeDefKind::Resource => true, + | TypeDefKind::Resource + | TypeDefKind::FixedSizeList(..) => true, TypeDefKind::Type(Type::Id(t)) => { needs_generics(resolve, &resolve.types[*t].kind) } TypeDefKind::Type(Type::String) => true, TypeDefKind::Type(_) => false, TypeDefKind::Unknown => unreachable!(), - TypeDefKind::FixedSizeList(..) => todo!(), TypeDefKind::Map(..) => todo!(), } } @@ -189,7 +189,9 @@ pub trait RustGenerator<'a> { TypeDefKind::Type(t) => self.ty(t, mode), TypeDefKind::Unknown => unreachable!(), - TypeDefKind::FixedSizeList(..) => todo!(), + TypeDefKind::FixedSizeList(t, size) => { + format!("[{}; {}]", self.ty(t, mode), size) + } TypeDefKind::Map(..) => todo!(), } } diff --git a/crates/wit-bindgen/src/types.rs b/crates/wit-bindgen/src/types.rs index 484c525a7592..f16ce7658dc2 100644 --- a/crates/wit-bindgen/src/types.rs +++ b/crates/wit-bindgen/src/types.rs @@ -157,7 +157,9 @@ impl Types { TypeDefKind::Handle(_) => info.has_handle = true, TypeDefKind::Resource => {} TypeDefKind::Unknown => unreachable!(), - TypeDefKind::FixedSizeList(..) => todo!(), + TypeDefKind::FixedSizeList(ty, _) => { + info = self.type_info(resolve, ty); + } TypeDefKind::Map(..) => todo!(), } self.type_info.insert(ty, info);