Path: blob/main/crates/test-util/src/component_fuzz.rs
3069 views
//! This module generates test cases for the Wasmtime component model function APIs,1//! e.g. `wasmtime::component::func::Func` and `TypedFunc`.2//!3//! Each case includes a list of arbitrary interface types to use as parameters, plus another one to use as a4//! result, and a component which exports a function and imports a function. The exported function forwards its5//! parameters to the imported one and forwards the result back to the caller. This serves to exercise Wasmtime's6//! lifting and lowering code and verify the values remain intact during both processes.78use arbitrary::{Arbitrary, Unstructured};9use indexmap::IndexSet;10use proc_macro2::{Ident, TokenStream};11use quote::{ToTokens, format_ident, quote};12use std::borrow::Cow;13use std::fmt::{self, Debug, Write};14use std::hash::{Hash, Hasher};15use std::iter;16use std::ops::Deref;17use wasmtime_component_util::{DiscriminantSize, FlagsSize, REALLOC_AND_FREE};1819const MAX_FLAT_PARAMS: usize = 16;20const MAX_FLAT_ASYNC_PARAMS: usize = 4;21const MAX_FLAT_RESULTS: usize = 1;2223/// The name of the imported host function which the generated component will call24pub const IMPORT_FUNCTION: &str = "echo-import";2526/// The name of the exported guest function which the host should call27pub const EXPORT_FUNCTION: &str = "echo-export";2829/// Wasmtime allows up to 100 type depth so limit this to just under that.30pub const MAX_TYPE_DEPTH: u32 = 99;3132macro_rules! uwriteln {33($($arg:tt)*) => {34writeln!($($arg)*).unwrap()35};36}3738macro_rules! uwrite {39($($arg:tt)*) => {40write!($($arg)*).unwrap()41};42}4344#[derive(Debug, Copy, Clone, PartialEq, Eq)]45enum CoreType {46I32,47I64,48F32,49F64,50}5152impl CoreType {53/// This is the `join` operation specified in [the canonical54/// ABI](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening) for55/// variant types.56fn join(self, other: Self) -> Self {57match (self, other) {58_ if self == other => self,59(Self::I32, Self::F32) | (Self::F32, Self::I32) => Self::I32,60_ => Self::I64,61}62}63}6465impl fmt::Display for CoreType {66fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {67match self {68Self::I32 => f.write_str("i32"),69Self::I64 => f.write_str("i64"),70Self::F32 => f.write_str("f32"),71Self::F64 => f.write_str("f64"),72}73}74}7576/// Wraps a `Box<[T]>` and provides an `Arbitrary` implementation that always generates slices of length less than77/// or equal to the longest tuple for which Wasmtime generates a `ComponentType` impl78#[derive(Debug, Clone)]79pub struct VecInRange<T, const L: u32, const H: u32>(Vec<T>);8081impl<T, const L: u32, const H: u32> VecInRange<T, L, H> {82fn new<'a>(83input: &mut Unstructured<'a>,84fuel: &mut u32,85generate: impl Fn(&mut Unstructured<'a>, &mut u32) -> arbitrary::Result<T>,86) -> arbitrary::Result<Self> {87let mut ret = Vec::new();88input.arbitrary_loop(Some(L), Some(H), |input| {89if *fuel > 0 {90*fuel = *fuel - 1;91ret.push(generate(input, fuel)?);92Ok(std::ops::ControlFlow::Continue(()))93} else {94Ok(std::ops::ControlFlow::Break(()))95}96})?;97Ok(Self(ret))98}99}100101impl<T, const L: u32, const H: u32> Deref for VecInRange<T, L, H> {102type Target = [T];103104fn deref(&self) -> &[T] {105self.0.deref()106}107}108109/// Represents a component model interface type110#[expect(missing_docs, reason = "self-describing")]111#[derive(Debug, Clone)]112pub enum Type {113Bool,114S8,115U8,116S16,117U16,118S32,119U32,120S64,121U64,122Float32,123Float64,124Char,125String,126List(Box<Type>),127128// Give records the ability to generate a generous amount of fields but129// don't let the fuzzer go too wild since `wasmparser`'s validator currently130// has hard limits in the 1000-ish range on the number of fields a record131// may contain.132Record(VecInRange<Type, 1, 200>),133134// Tuples can only have up to 16 type parameters in wasmtime right now for135// the static API, but the standard library only supports `Debug` up to 11136// elements, so compromise at an even 10.137Tuple(VecInRange<Type, 1, 10>),138139// Like records, allow a good number of variants, but variants require at140// least one case.141Variant(VecInRange<Option<Type>, 1, 200>),142Enum(u32),143144Option(Box<Type>),145Result {146ok: Option<Box<Type>>,147err: Option<Box<Type>>,148},149150Flags(u32),151}152153impl Type {154pub fn generate(155u: &mut Unstructured<'_>,156depth: u32,157fuel: &mut u32,158) -> arbitrary::Result<Type> {159*fuel = fuel.saturating_sub(1);160let max = if depth == 0 || *fuel == 0 { 12 } else { 20 };161Ok(match u.int_in_range(0..=max)? {1620 => Type::Bool,1631 => Type::S8,1642 => Type::U8,1653 => Type::S16,1664 => Type::U16,1675 => Type::S32,1686 => Type::U32,1697 => Type::S64,1708 => Type::U64,1719 => Type::Float32,17210 => Type::Float64,17311 => Type::Char,17412 => Type::String,175// ^-- if you add something here update the `depth == 0` case above17613 => Type::List(Box::new(Type::generate(u, depth - 1, fuel)?)),17714 => Type::Record(Type::generate_list(u, depth - 1, fuel)?),17815 => Type::Tuple(Type::generate_list(u, depth - 1, fuel)?),17916 => Type::Variant(VecInRange::new(u, fuel, |u, fuel| {180Type::generate_opt(u, depth - 1, fuel)181})?),18217 => {183let amt = u.int_in_range(1..=(*fuel).max(1).min(257))?;184*fuel -= amt;185Type::Enum(amt)186}18718 => Type::Option(Box::new(Type::generate(u, depth - 1, fuel)?)),18819 => Type::Result {189ok: Type::generate_opt(u, depth - 1, fuel)?.map(Box::new),190err: Type::generate_opt(u, depth - 1, fuel)?.map(Box::new),191},19220 => {193let amt = u.int_in_range(1..=(*fuel).min(32))?;194*fuel -= amt;195Type::Flags(amt)196}197// ^-- if you add something here update the `depth != 0` case above198_ => unreachable!(),199})200}201202fn generate_opt(203u: &mut Unstructured<'_>,204depth: u32,205fuel: &mut u32,206) -> arbitrary::Result<Option<Type>> {207Ok(if u.arbitrary()? {208Some(Type::generate(u, depth, fuel)?)209} else {210None211})212}213214fn generate_list<const L: u32, const H: u32>(215u: &mut Unstructured<'_>,216depth: u32,217fuel: &mut u32,218) -> arbitrary::Result<VecInRange<Type, L, H>> {219VecInRange::new(u, fuel, |u, fuel| Type::generate(u, depth, fuel))220}221222/// Generates text format wasm into `s` to store a value of this type, in223/// its flat representation stored in the `locals` provided, to the local224/// named `ptr` at the `offset` provided.225///226/// This will register helper functions necessary in `helpers`. The227/// `locals` iterator will be advanced for all locals consumed by this228/// store operation.229fn store_flat<'a>(230&'a self,231s: &mut String,232ptr: &str,233offset: u32,234locals: &mut dyn Iterator<Item = FlatSource>,235helpers: &mut IndexSet<Helper<'a>>,236) {237enum Kind {238Primitive(&'static str),239PointerPair,240Helper,241}242let kind = match self {243Type::Bool | Type::S8 | Type::U8 => Kind::Primitive("i32.store8"),244Type::S16 | Type::U16 => Kind::Primitive("i32.store16"),245Type::S32 | Type::U32 | Type::Char => Kind::Primitive("i32.store"),246Type::S64 | Type::U64 => Kind::Primitive("i64.store"),247Type::Float32 => Kind::Primitive("f32.store"),248Type::Float64 => Kind::Primitive("f64.store"),249Type::String | Type::List(_) => Kind::PointerPair,250Type::Enum(n) if *n <= (1 << 8) => Kind::Primitive("i32.store8"),251Type::Enum(n) if *n <= (1 << 16) => Kind::Primitive("i32.store16"),252Type::Enum(_) => Kind::Primitive("i32.store"),253Type::Flags(n) if *n <= 8 => Kind::Primitive("i32.store8"),254Type::Flags(n) if *n <= 16 => Kind::Primitive("i32.store16"),255Type::Flags(n) if *n <= 32 => Kind::Primitive("i32.store"),256Type::Flags(_) => unreachable!(),257Type::Record(_)258| Type::Tuple(_)259| Type::Variant(_)260| Type::Option(_)261| Type::Result { .. } => Kind::Helper,262};263264match kind {265Kind::Primitive(op) => uwriteln!(266s,267"({op} offset={offset} (local.get {ptr}) {})",268locals.next().unwrap()269),270Kind::PointerPair => {271let abi_ptr = locals.next().unwrap();272let abi_len = locals.next().unwrap();273uwriteln!(s, "(i32.store offset={offset} (local.get {ptr}) {abi_ptr})",);274let offset = offset + 4;275uwriteln!(s, "(i32.store offset={offset} (local.get {ptr}) {abi_len})",);276}277Kind::Helper => {278let (index, _) = helpers.insert_full(Helper(self));279uwriteln!(s, "(i32.add (local.get {ptr}) (i32.const {offset}))");280for _ in 0..self.lowered().len() {281let i = locals.next().unwrap();282uwriteln!(s, "{i}");283}284uwriteln!(s, "call $store_helper_{index}");285}286}287}288289/// Generates a text-format wasm function which takes a pointer and this290/// type's flat representation as arguments and then stores this value in291/// the first argument.292///293/// This is used to store records/variants to cut down on the size of final294/// functions and make codegen here a bit easier.295fn store_flat_helper<'a>(296&'a self,297s: &mut String,298i: usize,299helpers: &mut IndexSet<Helper<'a>>,300) {301uwrite!(s, "(func $store_helper_{i} (param i32)");302let lowered = self.lowered();303for ty in &lowered {304uwrite!(s, " (param {ty})");305}306s.push_str("\n");307let locals = (0..lowered.len() as u32).map(|i| i + 1).collect::<Vec<_>>();308let record = |s: &mut String, helpers: &mut IndexSet<Helper<'a>>, types: &'a [Type]| {309let mut locals = locals.iter().cloned().map(FlatSource::Local);310for (offset, ty) in record_field_offsets(types) {311ty.store_flat(s, "0", offset, &mut locals, helpers);312}313assert!(locals.next().is_none());314};315let variant = |s: &mut String,316helpers: &mut IndexSet<Helper<'a>>,317types: &[Option<&'a Type>]| {318let (size, offset) = variant_memory_info(types.iter().cloned());319// One extra block for out-of-bounds discriminants.320for _ in 0..types.len() + 1 {321s.push_str("block\n");322}323324// Store the discriminant in memory, then branch on it to figure325// out which case we're in.326let store = match size {327DiscriminantSize::Size1 => "i32.store8",328DiscriminantSize::Size2 => "i32.store16",329DiscriminantSize::Size4 => "i32.store",330};331uwriteln!(s, "({store} (local.get 0) (local.get 1))");332s.push_str("local.get 1\n");333s.push_str("br_table");334for i in 0..types.len() + 1 {335uwrite!(s, " {i}");336}337s.push_str("\nend\n");338339// Store each payload individually while converting locals from340// their source types to the precise type necessary for this341// variant.342for ty in types {343if let Some(ty) = ty {344let ty_lowered = ty.lowered();345let mut locals = locals[1..].iter().zip(&lowered[1..]).zip(&ty_lowered).map(346|((i, from), to)| FlatSource::LocalConvert {347local: *i,348from: *from,349to: *to,350},351);352ty.store_flat(s, "0", offset, &mut locals, helpers);353}354s.push_str("return\n");355s.push_str("end\n");356}357358// Catch-all result which is for out-of-bounds discriminants.359s.push_str("unreachable\n");360};361match self {362Type::Bool363| Type::S8364| Type::U8365| Type::S16366| Type::U16367| Type::S32368| Type::U32369| Type::Char370| Type::S64371| Type::U64372| Type::Float32373| Type::Float64374| Type::String375| Type::List(_)376| Type::Flags(_)377| Type::Enum(_) => unreachable!(),378379Type::Record(r) => record(s, helpers, r),380Type::Tuple(t) => record(s, helpers, t),381Type::Variant(v) => variant(382s,383helpers,384&v.iter().map(|t| t.as_ref()).collect::<Vec<_>>(),385),386Type::Option(o) => variant(s, helpers, &[None, Some(&**o)]),387Type::Result { ok, err } => variant(s, helpers, &[ok.as_deref(), err.as_deref()]),388};389s.push_str(")\n");390}391392/// Same as `store_flat`, except loads the flat values from `ptr+offset`.393///394/// Results are placed directly on the wasm stack.395fn load_flat<'a>(396&'a self,397s: &mut String,398ptr: &str,399offset: u32,400helpers: &mut IndexSet<Helper<'a>>,401) {402enum Kind {403Primitive(&'static str),404PointerPair,405Helper,406}407let kind = match self {408Type::Bool | Type::U8 => Kind::Primitive("i32.load8_u"),409Type::S8 => Kind::Primitive("i32.load8_s"),410Type::U16 => Kind::Primitive("i32.load16_u"),411Type::S16 => Kind::Primitive("i32.load16_s"),412Type::U32 | Type::S32 | Type::Char => Kind::Primitive("i32.load"),413Type::U64 | Type::S64 => Kind::Primitive("i64.load"),414Type::Float32 => Kind::Primitive("f32.load"),415Type::Float64 => Kind::Primitive("f64.load"),416Type::String | Type::List(_) => Kind::PointerPair,417Type::Enum(n) if *n <= (1 << 8) => Kind::Primitive("i32.load8_u"),418Type::Enum(n) if *n <= (1 << 16) => Kind::Primitive("i32.load16_u"),419Type::Enum(_) => Kind::Primitive("i32.load"),420Type::Flags(n) if *n <= 8 => Kind::Primitive("i32.load8_u"),421Type::Flags(n) if *n <= 16 => Kind::Primitive("i32.load16_u"),422Type::Flags(n) if *n <= 32 => Kind::Primitive("i32.load"),423Type::Flags(_) => unreachable!(),424425Type::Record(_)426| Type::Tuple(_)427| Type::Variant(_)428| Type::Option(_)429| Type::Result { .. } => Kind::Helper,430};431match kind {432Kind::Primitive(op) => uwriteln!(s, "({op} offset={offset} (local.get {ptr}))"),433Kind::PointerPair => {434uwriteln!(s, "(i32.load offset={offset} (local.get {ptr}))",);435let offset = offset + 4;436uwriteln!(s, "(i32.load offset={offset} (local.get {ptr}))",);437}438Kind::Helper => {439let (index, _) = helpers.insert_full(Helper(self));440uwriteln!(s, "(i32.add (local.get {ptr}) (i32.const {offset}))");441uwriteln!(s, "call $load_helper_{index}");442}443}444}445446/// Same as `store_flat_helper` but for loading the flat representation.447fn load_flat_helper<'a>(448&'a self,449s: &mut String,450i: usize,451helpers: &mut IndexSet<Helper<'a>>,452) {453uwrite!(s, "(func $load_helper_{i} (param i32)");454let lowered = self.lowered();455for ty in &lowered {456uwrite!(s, " (result {ty})");457}458s.push_str("\n");459let record = |s: &mut String, helpers: &mut IndexSet<Helper<'a>>, types: &'a [Type]| {460for (offset, ty) in record_field_offsets(types) {461ty.load_flat(s, "0", offset, helpers);462}463};464let variant = |s: &mut String,465helpers: &mut IndexSet<Helper<'a>>,466types: &[Option<&'a Type>]| {467let (size, offset) = variant_memory_info(types.iter().cloned());468469// Destination locals where the flat representation will be stored.470// These are automatically zero which handles unused fields too.471for (i, ty) in lowered.iter().enumerate() {472uwriteln!(s, " (local $r{i} {ty})");473}474475// Return block each case jumps to after setting all locals.476s.push_str("block $r\n");477478// One extra block for "out of bounds discriminant".479for _ in 0..types.len() + 1 {480s.push_str("block\n");481}482483// Load the discriminant and branch on it, storing it in484// `$r0` as well which is the first flat local representation.485let load = match size {486DiscriminantSize::Size1 => "i32.load8_u",487DiscriminantSize::Size2 => "i32.load16",488DiscriminantSize::Size4 => "i32.load",489};490uwriteln!(s, "({load} (local.get 0))");491s.push_str("local.tee $r0\n");492s.push_str("br_table");493for i in 0..types.len() + 1 {494uwrite!(s, " {i}");495}496s.push_str("\nend\n");497498// For each payload, which is in its own block, load payloads from499// memory as necessary and convert them into the final locals.500for ty in types {501if let Some(ty) = ty {502let ty_lowered = ty.lowered();503ty.load_flat(s, "0", offset, helpers);504for (i, (from, to)) in ty_lowered.iter().zip(&lowered[1..]).enumerate().rev() {505let i = i + 1;506match (from, to) {507(CoreType::F32, CoreType::I32) => {508s.push_str("i32.reinterpret_f32\n");509}510(CoreType::I32, CoreType::I64) => {511s.push_str("i64.extend_i32_u\n");512}513(CoreType::F32, CoreType::I64) => {514s.push_str("i32.reinterpret_f32\n");515s.push_str("i64.extend_i32_u\n");516}517(CoreType::F64, CoreType::I64) => {518s.push_str("i64.reinterpret_f64\n");519}520(a, b) if a == b => {}521_ => unimplemented!("convert {from:?} to {to:?}"),522}523uwriteln!(s, "local.set $r{i}");524}525}526s.push_str("br $r\n");527s.push_str("end\n");528}529530// The catch-all block for out-of-bounds discriminants.531s.push_str("unreachable\n");532s.push_str("end\n");533for i in 0..lowered.len() {534uwriteln!(s, " local.get $r{i}");535}536};537538match self {539Type::Bool540| Type::S8541| Type::U8542| Type::S16543| Type::U16544| Type::S32545| Type::U32546| Type::Char547| Type::S64548| Type::U64549| Type::Float32550| Type::Float64551| Type::String552| Type::List(_)553| Type::Flags(_)554| Type::Enum(_) => unreachable!(),555556Type::Record(r) => record(s, helpers, r),557Type::Tuple(t) => record(s, helpers, t),558Type::Variant(v) => variant(559s,560helpers,561&v.iter().map(|t| t.as_ref()).collect::<Vec<_>>(),562),563Type::Option(o) => variant(s, helpers, &[None, Some(&**o)]),564Type::Result { ok, err } => variant(s, helpers, &[ok.as_deref(), err.as_deref()]),565};566s.push_str(")\n");567}568}569570#[derive(Clone)]571enum FlatSource {572Local(u32),573LocalConvert {574local: u32,575from: CoreType,576to: CoreType,577},578}579580impl fmt::Display for FlatSource {581fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {582match self {583FlatSource::Local(i) => write!(f, "(local.get {i})"),584FlatSource::LocalConvert { local, from, to } => {585match (from, to) {586(a, b) if a == b => write!(f, "(local.get {local})"),587(CoreType::I32, CoreType::F32) => {588write!(f, "(f32.reinterpret_i32 (local.get {local}))")589}590(CoreType::I64, CoreType::I32) => {591write!(f, "(i32.wrap_i64 (local.get {local}))")592}593(CoreType::I64, CoreType::F64) => {594write!(f, "(f64.reinterpret_i64 (local.get {local}))")595}596(CoreType::I64, CoreType::F32) => {597write!(598f,599"(f32.reinterpret_i32 (i32.wrap_i64 (local.get {local})))"600)601}602_ => unimplemented!("convert {from:?} to {to:?}"),603}604// ..605}606}607}608}609610fn lower_record<'a>(types: impl Iterator<Item = &'a Type>, vec: &mut Vec<CoreType>) {611for ty in types {612ty.lower(vec);613}614}615616fn lower_variant<'a>(types: impl Iterator<Item = Option<&'a Type>>, vec: &mut Vec<CoreType>) {617vec.push(CoreType::I32);618let offset = vec.len();619for ty in types {620let ty = match ty {621Some(ty) => ty,622None => continue,623};624for (index, ty) in ty.lowered().iter().enumerate() {625let index = offset + index;626if index < vec.len() {627vec[index] = vec[index].join(*ty);628} else {629vec.push(*ty)630}631}632}633}634635fn u32_count_from_flag_count(count: usize) -> usize {636match FlagsSize::from_count(count) {637FlagsSize::Size0 => 0,638FlagsSize::Size1 | FlagsSize::Size2 => 1,639FlagsSize::Size4Plus(n) => n.into(),640}641}642643struct SizeAndAlignment {644size: usize,645alignment: u32,646}647648impl Type {649fn lowered(&self) -> Vec<CoreType> {650let mut vec = Vec::new();651self.lower(&mut vec);652vec653}654655fn lower(&self, vec: &mut Vec<CoreType>) {656match self {657Type::Bool658| Type::U8659| Type::S8660| Type::S16661| Type::U16662| Type::S32663| Type::U32664| Type::Char665| Type::Enum(_) => vec.push(CoreType::I32),666Type::S64 | Type::U64 => vec.push(CoreType::I64),667Type::Float32 => vec.push(CoreType::F32),668Type::Float64 => vec.push(CoreType::F64),669Type::String | Type::List(_) => {670vec.push(CoreType::I32);671vec.push(CoreType::I32);672}673Type::Record(types) => lower_record(types.iter(), vec),674Type::Tuple(types) => lower_record(types.0.iter(), vec),675Type::Variant(types) => lower_variant(types.0.iter().map(|t| t.as_ref()), vec),676Type::Option(ty) => lower_variant([None, Some(&**ty)].into_iter(), vec),677Type::Result { ok, err } => {678lower_variant([ok.as_deref(), err.as_deref()].into_iter(), vec)679}680Type::Flags(count) => vec.extend(681iter::repeat(CoreType::I32).take(u32_count_from_flag_count(*count as usize)),682),683}684}685686fn size_and_alignment(&self) -> SizeAndAlignment {687match self {688Type::Bool | Type::S8 | Type::U8 => SizeAndAlignment {689size: 1,690alignment: 1,691},692693Type::S16 | Type::U16 => SizeAndAlignment {694size: 2,695alignment: 2,696},697698Type::S32 | Type::U32 | Type::Char | Type::Float32 => SizeAndAlignment {699size: 4,700alignment: 4,701},702703Type::S64 | Type::U64 | Type::Float64 => SizeAndAlignment {704size: 8,705alignment: 8,706},707708Type::String | Type::List(_) => SizeAndAlignment {709size: 8,710alignment: 4,711},712713Type::Record(types) => record_size_and_alignment(types.iter()),714715Type::Tuple(types) => record_size_and_alignment(types.0.iter()),716717Type::Variant(types) => variant_size_and_alignment(types.0.iter().map(|t| t.as_ref())),718719Type::Enum(count) => variant_size_and_alignment((0..*count).map(|_| None)),720721Type::Option(ty) => variant_size_and_alignment([None, Some(&**ty)].into_iter()),722723Type::Result { ok, err } => {724variant_size_and_alignment([ok.as_deref(), err.as_deref()].into_iter())725}726727Type::Flags(count) => match FlagsSize::from_count(*count as usize) {728FlagsSize::Size0 => SizeAndAlignment {729size: 0,730alignment: 1,731},732FlagsSize::Size1 => SizeAndAlignment {733size: 1,734alignment: 1,735},736FlagsSize::Size2 => SizeAndAlignment {737size: 2,738alignment: 2,739},740FlagsSize::Size4Plus(n) => SizeAndAlignment {741size: usize::from(n) * 4,742alignment: 4,743},744},745}746}747}748749fn align_to(a: usize, align: u32) -> usize {750let align = align as usize;751(a + (align - 1)) & !(align - 1)752}753754fn record_field_offsets<'a>(755types: impl IntoIterator<Item = &'a Type>,756) -> impl Iterator<Item = (u32, &'a Type)> {757let mut offset = 0;758types.into_iter().map(move |ty| {759let SizeAndAlignment { size, alignment } = ty.size_and_alignment();760let ret = align_to(offset, alignment);761offset = ret + size;762(ret as u32, ty)763})764}765766fn record_size_and_alignment<'a>(types: impl IntoIterator<Item = &'a Type>) -> SizeAndAlignment {767let mut offset = 0;768let mut align = 1;769for ty in types {770let SizeAndAlignment { size, alignment } = ty.size_and_alignment();771offset = align_to(offset, alignment) + size;772align = align.max(alignment);773}774775SizeAndAlignment {776size: align_to(offset, align),777alignment: align,778}779}780781fn variant_size_and_alignment<'a>(782types: impl ExactSizeIterator<Item = Option<&'a Type>>,783) -> SizeAndAlignment {784let discriminant_size = DiscriminantSize::from_count(types.len()).unwrap();785let mut alignment = u32::from(discriminant_size);786let mut size = 0;787for ty in types {788if let Some(ty) = ty {789let size_and_alignment = ty.size_and_alignment();790alignment = alignment.max(size_and_alignment.alignment);791size = size.max(size_and_alignment.size);792}793}794795SizeAndAlignment {796size: align_to(797align_to(usize::from(discriminant_size), alignment) + size,798alignment,799),800alignment,801}802}803804fn variant_memory_info<'a>(805types: impl ExactSizeIterator<Item = Option<&'a Type>>,806) -> (DiscriminantSize, u32) {807let discriminant_size = DiscriminantSize::from_count(types.len()).unwrap();808let mut alignment = u32::from(discriminant_size);809for ty in types {810if let Some(ty) = ty {811let size_and_alignment = ty.size_and_alignment();812alignment = alignment.max(size_and_alignment.alignment);813}814}815816(817discriminant_size,818align_to(usize::from(discriminant_size), alignment) as u32,819)820}821822/// Generates the internals of a core wasm module which imports a single823/// component function `IMPORT_FUNCTION` and exports a single component824/// function `EXPORT_FUNCTION`.825///826/// The component function takes `params` as arguments and optionally returns827/// `result`. The `lift_abi` and `lower_abi` fields indicate the ABI in-use for828/// this operation.829fn make_import_and_export(830params: &[&Type],831result: Option<&Type>,832lift_abi: LiftAbi,833lower_abi: LowerAbi,834) -> String {835let params_lowered = params836.iter()837.flat_map(|ty| ty.lowered())838.collect::<Box<[_]>>();839let result_lowered = result.map(|t| t.lowered()).unwrap_or(Vec::new());840841let mut wat = String::new();842843enum Location {844Flat,845Indirect(u32),846}847848// Generate the core wasm type corresponding to the imported function being849// lowered with `lower_abi`.850wat.push_str(&format!("(type $import (func"));851let max_import_params = match lower_abi {852LowerAbi::Sync => MAX_FLAT_PARAMS,853LowerAbi::Async => MAX_FLAT_ASYNC_PARAMS,854};855let (import_params_loc, nparams) = push_params(&mut wat, ¶ms_lowered, max_import_params);856let import_results_loc = match lower_abi {857LowerAbi::Sync => {858push_result_or_retptr(&mut wat, &result_lowered, nparams, MAX_FLAT_RESULTS)859}860LowerAbi::Async => {861let loc = if result.is_none() {862Location::Flat863} else {864wat.push_str(" (param i32)"); // result pointer865Location::Indirect(nparams)866};867wat.push_str(" (result i32)"); // status code868loc869}870};871wat.push_str("))\n");872873// Generate the import function.874wat.push_str(&format!(875r#"(import "host" "{IMPORT_FUNCTION}" (func $host (type $import)))"#876));877878// Do the same as above for the exported function's type which is lifted879// with `lift_abi`.880//881// Note that `export_results_loc` being `None` means that `task.return` is882// used to communicate results.883wat.push_str(&format!("(type $export (func"));884let (export_params_loc, _nparams) = push_params(&mut wat, ¶ms_lowered, MAX_FLAT_PARAMS);885let export_results_loc = match lift_abi {886LiftAbi::Sync => Some(push_group(&mut wat, "result", &result_lowered, MAX_FLAT_RESULTS).0),887LiftAbi::AsyncCallback => {888wat.push_str(" (result i32)"); // status code889None890}891LiftAbi::AsyncStackful => None,892};893wat.push_str("))\n");894895// If the export is async, generate `task.return` as an import as well896// which is necesary to communicate the results.897if export_results_loc.is_none() {898wat.push_str(&format!("(type $task.return (func"));899push_params(&mut wat, &result_lowered, MAX_FLAT_PARAMS);900wat.push_str("))\n");901wat.push_str(&format!(902r#"(import "" "task.return" (func $task.return (type $task.return)))"#903));904}905906wat.push_str(&format!(907r#"908(func (export "{EXPORT_FUNCTION}") (type $export)909(local $retptr i32)910(local $argptr i32)911"#912));913let mut store_helpers = IndexSet::new();914let mut load_helpers = IndexSet::new();915916match (export_params_loc, import_params_loc) {917// flat => flat is just moving locals around918(Location::Flat, Location::Flat) => {919for (index, _) in params_lowered.iter().enumerate() {920uwrite!(wat, "local.get {index}\n");921}922}923924// indirect => indirect is just moving locals around925(Location::Indirect(i), Location::Indirect(j)) => {926assert_eq!(j, 0);927uwrite!(wat, "local.get {i}\n");928}929930// flat => indirect means that all parameters are stored in memory as931// if it was a record of all the parameters.932(Location::Flat, Location::Indirect(_)) => {933let SizeAndAlignment { size, alignment } =934record_size_and_alignment(params.iter().cloned());935wat.push_str(&format!(936r#"937(local.set $argptr938(call $realloc939(i32.const 0)940(i32.const 0)941(i32.const {alignment})942(i32.const {size})))943local.get $argptr944"#945));946let mut locals = (0..params_lowered.len() as u32).map(FlatSource::Local);947for (offset, ty) in record_field_offsets(params.iter().cloned()) {948ty.store_flat(&mut wat, "$argptr", offset, &mut locals, &mut store_helpers);949}950assert!(locals.next().is_none());951}952953(Location::Indirect(_), Location::Flat) => unreachable!(),954}955956// Pass a return-pointer if necessary.957match import_results_loc {958Location::Flat => {}959Location::Indirect(_) => {960let SizeAndAlignment { size, alignment } = result.unwrap().size_and_alignment();961962wat.push_str(&format!(963r#"964(local.set $retptr965(call $realloc966(i32.const 0)967(i32.const 0)968(i32.const {alignment})969(i32.const {size})))970local.get $retptr971"#972));973}974}975976wat.push_str("call $host\n");977978// Assert the lowered call is ready if an async code was returned.979//980// TODO: handle when the import isn't ready yet981if let LowerAbi::Async = lower_abi {982wat.push_str("i32.const 2\n");983wat.push_str("i32.ne\n");984wat.push_str("if unreachable end\n");985}986987// TODO: conditionally inject a yield here988989match (import_results_loc, export_results_loc) {990// flat => flat results involves nothing, the results are already on991// the stack.992(Location::Flat, Some(Location::Flat)) => {}993994// indirect => indirect results requires returning the `$retptr` the995// host call filled in.996(Location::Indirect(_), Some(Location::Indirect(_))) => {997wat.push_str("local.get $retptr\n");998}9991000// indirect => flat requires loading the result from the return pointer1001(Location::Indirect(_), Some(Location::Flat)) => {1002result1003.unwrap()1004.load_flat(&mut wat, "$retptr", 0, &mut load_helpers);1005}10061007// flat => task.return is easy, the results are already there so just1008// call the function.1009(Location::Flat, None) => {1010wat.push_str("call $task.return\n");1011}10121013// indirect => task.return needs to forward `$retptr` if the results1014// are indirect, or otherwise it must be loaded from memory to a flat1015// representation.1016(Location::Indirect(_), None) => {1017if result_lowered.len() <= MAX_FLAT_PARAMS {1018result1019.unwrap()1020.load_flat(&mut wat, "$retptr", 0, &mut load_helpers);1021} else {1022wat.push_str("local.get $retptr\n");1023}1024wat.push_str("call $task.return\n");1025}10261027(Location::Flat, Some(Location::Indirect(_))) => unreachable!(),1028}10291030if let LiftAbi::AsyncCallback = lift_abi {1031wat.push_str("i32.const 0\n"); // completed status code1032}10331034wat.push_str(")\n");10351036// Generate a `callback` function for the callback ABI.1037//1038// TODO: fill this in1039if let LiftAbi::AsyncCallback = lift_abi {1040wat.push_str(1041r#"1042(func (export "callback") (param i32 i32 i32) (result i32) unreachable)1043"#,1044);1045}10461047// Fill out all store/load helpers that were needed during generation1048// above. This is a fix-point-loop since each helper may end up requiring1049// more helpers.1050let mut i = 0;1051while i < store_helpers.len() {1052let ty = store_helpers[i].0;1053ty.store_flat_helper(&mut wat, i, &mut store_helpers);1054i += 1;1055}1056i = 0;1057while i < load_helpers.len() {1058let ty = load_helpers[i].0;1059ty.load_flat_helper(&mut wat, i, &mut load_helpers);1060i += 1;1061}10621063return wat;10641065fn push_params(wat: &mut String, params: &[CoreType], max_flat: usize) -> (Location, u32) {1066push_group(wat, "param", params, max_flat)1067}10681069fn push_group(1070wat: &mut String,1071name: &str,1072params: &[CoreType],1073max_flat: usize,1074) -> (Location, u32) {1075let mut nparams = 0;1076let loc = if params.is_empty() {1077// nothing to emit...1078Location::Flat1079} else if params.len() <= max_flat {1080wat.push_str(&format!(" ({name}"));1081for ty in params {1082wat.push_str(&format!(" {ty}"));1083nparams += 1;1084}1085wat.push_str(")");1086Location::Flat1087} else {1088wat.push_str(&format!(" ({name} i32)"));1089nparams += 1;1090Location::Indirect(0)1091};1092(loc, nparams)1093}10941095fn push_result_or_retptr(1096wat: &mut String,1097results: &[CoreType],1098nparams: u32,1099max_flat: usize,1100) -> Location {1101if results.is_empty() {1102// nothing to emit...1103Location::Flat1104} else if results.len() <= max_flat {1105wat.push_str(" (result");1106for ty in results {1107wat.push_str(&format!(" {ty}"));1108}1109wat.push_str(")");1110Location::Flat1111} else {1112wat.push_str(" (param i32)");1113Location::Indirect(nparams)1114}1115}1116}11171118struct Helper<'a>(&'a Type);11191120impl Hash for Helper<'_> {1121fn hash<H: Hasher>(&self, h: &mut H) {1122std::ptr::hash(self.0, h);1123}1124}11251126impl PartialEq for Helper<'_> {1127fn eq(&self, other: &Self) -> bool {1128std::ptr::eq(self.0, other.0)1129}1130}11311132impl Eq for Helper<'_> {}11331134fn make_rust_name(name_counter: &mut u32) -> Ident {1135let name = format_ident!("Foo{name_counter}");1136*name_counter += 1;1137name1138}11391140/// Generate a [`TokenStream`] containing the rust type name for a type.1141///1142/// The `name_counter` parameter is used to generate names for each recursively visited type. The `declarations`1143/// parameter is used to accumulate declarations for each recursively visited type.1144pub fn rust_type(ty: &Type, name_counter: &mut u32, declarations: &mut TokenStream) -> TokenStream {1145match ty {1146Type::Bool => quote!(bool),1147Type::S8 => quote!(i8),1148Type::U8 => quote!(u8),1149Type::S16 => quote!(i16),1150Type::U16 => quote!(u16),1151Type::S32 => quote!(i32),1152Type::U32 => quote!(u32),1153Type::S64 => quote!(i64),1154Type::U64 => quote!(u64),1155Type::Float32 => quote!(Float32),1156Type::Float64 => quote!(Float64),1157Type::Char => quote!(char),1158Type::String => quote!(Box<str>),1159Type::List(ty) => {1160let ty = rust_type(ty, name_counter, declarations);1161quote!(Vec<#ty>)1162}1163Type::Record(types) => {1164let fields = types1165.iter()1166.enumerate()1167.map(|(index, ty)| {1168let name = format_ident!("f{index}");1169let ty = rust_type(ty, name_counter, declarations);1170quote!(#name: #ty,)1171})1172.collect::<TokenStream>();11731174let name = make_rust_name(name_counter);11751176declarations.extend(quote! {1177#[derive(ComponentType, Lift, Lower, PartialEq, Debug, Clone, Arbitrary)]1178#[component(record)]1179struct #name {1180#fields1181}1182});11831184quote!(#name)1185}1186Type::Tuple(types) => {1187let fields = types1188.01189.iter()1190.map(|ty| {1191let ty = rust_type(ty, name_counter, declarations);1192quote!(#ty,)1193})1194.collect::<TokenStream>();11951196quote!((#fields))1197}1198Type::Variant(types) => {1199let cases = types1200.01201.iter()1202.enumerate()1203.map(|(index, ty)| {1204let name = format_ident!("C{index}");1205let ty = match ty {1206Some(ty) => {1207let ty = rust_type(ty, name_counter, declarations);1208quote!((#ty))1209}1210None => quote!(),1211};1212quote!(#name #ty,)1213})1214.collect::<TokenStream>();12151216let name = make_rust_name(name_counter);1217declarations.extend(quote! {1218#[derive(ComponentType, Lift, Lower, PartialEq, Debug, Clone, Arbitrary)]1219#[component(variant)]1220enum #name {1221#cases1222}1223});12241225quote!(#name)1226}1227Type::Enum(count) => {1228let cases = (0..*count)1229.map(|index| {1230let name = format_ident!("E{index}");1231quote!(#name,)1232})1233.collect::<TokenStream>();12341235let name = make_rust_name(name_counter);1236let repr = match DiscriminantSize::from_count(*count as usize).unwrap() {1237DiscriminantSize::Size1 => quote!(u8),1238DiscriminantSize::Size2 => quote!(u16),1239DiscriminantSize::Size4 => quote!(u32),1240};12411242declarations.extend(quote! {1243#[derive(ComponentType, Lift, Lower, PartialEq, Debug, Copy, Clone, Arbitrary)]1244#[component(enum)]1245#[repr(#repr)]1246enum #name {1247#cases1248}1249});12501251quote!(#name)1252}1253Type::Option(ty) => {1254let ty = rust_type(ty, name_counter, declarations);1255quote!(Option<#ty>)1256}1257Type::Result { ok, err } => {1258let ok = match ok {1259Some(ok) => rust_type(ok, name_counter, declarations),1260None => quote!(()),1261};1262let err = match err {1263Some(err) => rust_type(err, name_counter, declarations),1264None => quote!(()),1265};1266quote!(Result<#ok, #err>)1267}1268Type::Flags(count) => {1269let type_name = make_rust_name(name_counter);12701271let mut flags = TokenStream::new();1272let mut names = TokenStream::new();12731274for index in 0..*count {1275let name = format_ident!("F{index}");1276flags.extend(quote!(const #name;));1277names.extend(quote!(#type_name::#name,))1278}12791280declarations.extend(quote! {1281wasmtime::component::flags! {1282#type_name {1283#flags1284}1285}12861287impl<'a> arbitrary::Arbitrary<'a> for #type_name {1288fn arbitrary(input: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {1289let mut flags = #type_name::default();1290for flag in [#names] {1291if input.arbitrary()? {1292flags |= flag;1293}1294}1295Ok(flags)1296}1297}1298});12991300quote!(#type_name)1301}1302}1303}13041305#[derive(Default)]1306struct TypesBuilder<'a> {1307next: u32,1308worklist: Vec<(u32, &'a Type)>,1309}13101311impl<'a> TypesBuilder<'a> {1312fn write_ref(&mut self, ty: &'a Type, dst: &mut String) {1313match ty {1314// Primitive types can be referenced directly1315Type::Bool => dst.push_str("bool"),1316Type::S8 => dst.push_str("s8"),1317Type::U8 => dst.push_str("u8"),1318Type::S16 => dst.push_str("s16"),1319Type::U16 => dst.push_str("u16"),1320Type::S32 => dst.push_str("s32"),1321Type::U32 => dst.push_str("u32"),1322Type::S64 => dst.push_str("s64"),1323Type::U64 => dst.push_str("u64"),1324Type::Float32 => dst.push_str("float32"),1325Type::Float64 => dst.push_str("float64"),1326Type::Char => dst.push_str("char"),1327Type::String => dst.push_str("string"),13281329// Otherwise emit a reference to the type and remember to generate1330// the corresponding type alias later.1331Type::List(_)1332| Type::Record(_)1333| Type::Tuple(_)1334| Type::Variant(_)1335| Type::Enum(_)1336| Type::Option(_)1337| Type::Result { .. }1338| Type::Flags(_) => {1339let idx = self.next;1340self.next += 1;1341uwrite!(dst, "$t{idx}");1342self.worklist.push((idx, ty));1343}1344}1345}13461347fn write_decl(&mut self, idx: u32, ty: &'a Type) -> String {1348let mut decl = format!("(type $t{idx}' ");1349match ty {1350Type::Bool1351| Type::S81352| Type::U81353| Type::S161354| Type::U161355| Type::S321356| Type::U321357| Type::S641358| Type::U641359| Type::Float321360| Type::Float641361| Type::Char1362| Type::String => unreachable!(),13631364Type::List(ty) => {1365decl.push_str("(list ");1366self.write_ref(ty, &mut decl);1367decl.push_str(")");1368}1369Type::Record(types) => {1370decl.push_str("(record");1371for (index, ty) in types.iter().enumerate() {1372uwrite!(decl, r#" (field "f{index}" "#);1373self.write_ref(ty, &mut decl);1374decl.push_str(")");1375}1376decl.push_str(")");1377}1378Type::Tuple(types) => {1379decl.push_str("(tuple");1380for ty in types.iter() {1381decl.push_str(" ");1382self.write_ref(ty, &mut decl);1383}1384decl.push_str(")");1385}1386Type::Variant(types) => {1387decl.push_str("(variant");1388for (index, ty) in types.iter().enumerate() {1389uwrite!(decl, r#" (case "C{index}""#);1390if let Some(ty) = ty {1391decl.push_str(" ");1392self.write_ref(ty, &mut decl);1393}1394decl.push_str(")");1395}1396decl.push_str(")");1397}1398Type::Enum(count) => {1399decl.push_str("(enum");1400for index in 0..*count {1401uwrite!(decl, r#" "E{index}""#);1402}1403decl.push_str(")");1404}1405Type::Option(ty) => {1406decl.push_str("(option ");1407self.write_ref(ty, &mut decl);1408decl.push_str(")");1409}1410Type::Result { ok, err } => {1411decl.push_str("(result");1412if let Some(ok) = ok {1413decl.push_str(" ");1414self.write_ref(ok, &mut decl);1415}1416if let Some(err) = err {1417decl.push_str(" (error ");1418self.write_ref(err, &mut decl);1419decl.push_str(")");1420}1421decl.push_str(")");1422}1423Type::Flags(count) => {1424decl.push_str("(flags");1425for index in 0..*count {1426uwrite!(decl, r#" "F{index}""#);1427}1428decl.push_str(")");1429}1430}1431decl.push_str(")\n");1432uwriteln!(decl, "(import \"t{idx}\" (type $t{idx} (eq $t{idx}')))");1433decl1434}1435}14361437/// Represents custom fragments of a WAT file which may be used to create a component for exercising [`TestCase`]s1438#[derive(Debug)]1439pub struct Declarations {1440/// Type declarations (if any) referenced by `params` and/or `result`1441pub types: Cow<'static, str>,1442/// Types to thread through when instantiating sub-components.1443pub type_instantiation_args: Cow<'static, str>,1444/// Parameter declarations used for the imported and exported functions1445pub params: Cow<'static, str>,1446/// Result declaration used for the imported and exported functions1447pub results: Cow<'static, str>,1448/// Implementation of the "caller" component, which invokes the `callee`1449/// composed component.1450pub caller_module: Cow<'static, str>,1451/// Implementation of the "callee" component, which invokes the host.1452pub callee_module: Cow<'static, str>,1453/// Options used for caller/calle ABI/etc.1454pub options: TestCaseOptions,1455}14561457impl Declarations {1458/// Generate a complete WAT file based on the specified fragments.1459pub fn make_component(&self) -> Box<str> {1460let Self {1461types,1462type_instantiation_args,1463params,1464results,1465caller_module,1466callee_module,1467options,1468} = self;1469let mk_component = |name: &str,1470module: &str,1471import_async: bool,1472export_async: bool,1473encoding: StringEncoding,1474lift_abi: LiftAbi,1475lower_abi: LowerAbi| {1476let import_async = if import_async { "async" } else { "" };1477let export_async = if export_async { "async" } else { "" };1478let lower_async_option = match lower_abi {1479LowerAbi::Sync => "",1480LowerAbi::Async => "async",1481};1482let lift_async_option = match lift_abi {1483LiftAbi::Sync => "",1484LiftAbi::AsyncStackful => "async",1485LiftAbi::AsyncCallback => "async (callback (func $i \"callback\"))",1486};14871488let mut intrinsic_defs = String::new();1489let mut intrinsic_imports = String::new();14901491match lift_abi {1492LiftAbi::Sync => {}1493LiftAbi::AsyncCallback | LiftAbi::AsyncStackful => {1494intrinsic_defs.push_str(&format!(1495r#"1496(core func $task.return (canon task.return {results}1497(memory $libc "memory") string-encoding={encoding}))1498"#,1499));1500intrinsic_imports.push_str(1501r#"1502(with "" (instance (export "task.return" (func $task.return))))1503"#,1504);1505}1506}15071508format!(1509r#"1510(component ${name}1511{types}1512(type $import_sig (func {import_async} {params} {results}))1513(type $export_sig (func {export_async} {params} {results}))1514(import "{IMPORT_FUNCTION}" (func $f (type $import_sig)))15151516(core instance $libc (instantiate $libc))15171518(core func $f_lower (canon lower1519(func $f)1520(memory $libc "memory")1521(realloc (func $libc "realloc"))1522string-encoding={encoding}1523{lower_async_option}1524))15251526{intrinsic_defs}15271528(core module $m1529(memory (import "libc" "memory") 1)1530(func $realloc (import "libc" "realloc") (param i32 i32 i32 i32) (result i32))15311532{module}1533)15341535(core instance $i (instantiate $m1536(with "libc" (instance $libc))1537(with "host" (instance (export "{IMPORT_FUNCTION}" (func $f_lower))))1538{intrinsic_imports}1539))15401541(func (export "{EXPORT_FUNCTION}") (type $export_sig)1542(canon lift1543(core func $i "{EXPORT_FUNCTION}")1544(memory $libc "memory")1545(realloc (func $libc "realloc"))1546string-encoding={encoding}1547{lift_async_option}1548)1549)1550)1551"#1552)1553};15541555let c1 = mk_component(1556"callee",1557&callee_module,1558options.host_async,1559options.guest_callee_async,1560options.callee_encoding,1561options.callee_lift_abi,1562options.callee_lower_abi,1563);1564let c2 = mk_component(1565"caller",1566&caller_module,1567options.guest_callee_async,1568options.guest_caller_async,1569options.caller_encoding,1570options.caller_lift_abi,1571options.caller_lower_abi,1572);1573let host_async = if options.host_async { "async" } else { "" };15741575format!(1576r#"1577(component1578(core module $libc1579(memory (export "memory") 1)1580{REALLOC_AND_FREE}1581)158215831584{types}15851586(type $host_sig (func {host_async} {params} {results}))1587(import "{IMPORT_FUNCTION}" (func $f (type $host_sig)))15881589{c1}1590{c2}1591(instance $c1 (instantiate $callee1592{type_instantiation_args}1593(with "{IMPORT_FUNCTION}" (func $f))1594))1595(instance $c2 (instantiate $caller1596{type_instantiation_args}1597(with "{IMPORT_FUNCTION}" (func $c1 "{EXPORT_FUNCTION}"))1598))1599(export "{EXPORT_FUNCTION}" (func $c2 "{EXPORT_FUNCTION}"))1600)"#,1601)1602.into()1603}1604}16051606/// Represents a test case for calling a component function1607#[derive(Debug)]1608pub struct TestCase<'a> {1609/// The types of parameters to pass to the function1610pub params: Vec<&'a Type>,1611/// The result types of the function1612pub result: Option<&'a Type>,1613/// ABI options to use for this test case.1614pub options: TestCaseOptions,1615}16161617/// Collection of options which configure how the caller/callee/etc ABIs are1618/// all configured.1619#[derive(Debug, Arbitrary, Copy, Clone)]1620pub struct TestCaseOptions {1621/// Whether or not the guest caller component (the entrypoint) is using an1622/// `async` function type.1623pub guest_caller_async: bool,1624/// Whether or not the guest callee component (what the entrypoint calls)1625/// is using an `async` function type.1626pub guest_callee_async: bool,1627/// Whether or not the host is using an async function type (what the1628/// guest callee calls).1629pub host_async: bool,1630/// The string encoding of the caller component.1631pub caller_encoding: StringEncoding,1632/// The string encoding of the callee component.1633pub callee_encoding: StringEncoding,1634/// The ABI that the caller component is using to lift its export (the main1635/// entrypoint).1636pub caller_lift_abi: LiftAbi,1637/// The ABI that the callee component is using to lift its export (called1638/// by the caller).1639pub callee_lift_abi: LiftAbi,1640/// The ABI that the caller component is using to lower its import (the1641/// callee's export).1642pub caller_lower_abi: LowerAbi,1643/// The ABI that the callee component is using to lower its import (the1644/// host function).1645pub callee_lower_abi: LowerAbi,1646}16471648#[derive(Debug, Arbitrary, Copy, Clone)]1649pub enum LiftAbi {1650Sync,1651AsyncStackful,1652AsyncCallback,1653}16541655#[derive(Debug, Arbitrary, Copy, Clone)]1656pub enum LowerAbi {1657Sync,1658Async,1659}16601661impl<'a> TestCase<'a> {1662pub fn generate(types: &'a [Type], u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {1663let max_params = if types.len() > 0 { 5 } else { 0 };1664let params = (0..u.int_in_range(0..=max_params)?)1665.map(|_| u.choose(&types))1666.collect::<arbitrary::Result<Vec<_>>>()?;1667let result = if types.len() > 0 && u.arbitrary()? {1668Some(u.choose(&types)?)1669} else {1670None1671};16721673let mut options = u.arbitrary::<TestCaseOptions>()?;16741675// Sync tasks cannot call async functions via a sync lower, nor can they1676// block in other ways (e.g. by calling `waitable-set.wait`, returning1677// `CALLBACK_CODE_WAIT`, etc.) prior to returning. Therefore,1678// async-ness cascades to the callers:1679if options.host_async {1680options.guest_callee_async = true;1681}1682if options.guest_callee_async {1683options.guest_caller_async = true;1684}16851686Ok(Self {1687params,1688result,1689options,1690})1691}16921693/// Generate a `Declarations` for this `TestCase` which may be used to build a component to execute the case.1694pub fn declarations(&self) -> Declarations {1695let mut builder = TypesBuilder::default();16961697let mut params = String::new();1698for (i, ty) in self.params.iter().enumerate() {1699params.push_str(&format!(" (param \"p{i}\" "));1700builder.write_ref(ty, &mut params);1701params.push_str(")");1702}17031704let mut results = String::new();1705if let Some(ty) = self.result {1706results.push_str(&format!(" (result "));1707builder.write_ref(ty, &mut results);1708results.push_str(")");1709}17101711let caller_module = make_import_and_export(1712&self.params,1713self.result,1714self.options.caller_lift_abi,1715self.options.caller_lower_abi,1716);1717let callee_module = make_import_and_export(1718&self.params,1719self.result,1720self.options.callee_lift_abi,1721self.options.callee_lower_abi,1722);17231724let mut type_decls = Vec::new();1725let mut type_instantiation_args = String::new();1726while let Some((idx, ty)) = builder.worklist.pop() {1727type_decls.push(builder.write_decl(idx, ty));1728uwriteln!(type_instantiation_args, "(with \"t{idx}\" (type $t{idx}))");1729}17301731// Note that types are printed here in reverse order since they were1732// pushed onto `type_decls` as they were referenced meaning the last one1733// is the "base" one.1734let mut types = String::new();1735for decl in type_decls.into_iter().rev() {1736types.push_str(&decl);1737types.push_str("\n");1738}17391740Declarations {1741types: types.into(),1742type_instantiation_args: type_instantiation_args.into(),1743params: params.into(),1744results: results.into(),1745caller_module: caller_module.into(),1746callee_module: callee_module.into(),1747options: self.options,1748}1749}1750}17511752#[derive(Copy, Clone, Debug, Arbitrary)]1753pub enum StringEncoding {1754Utf8,1755Utf16,1756Latin1OrUtf16,1757}17581759impl fmt::Display for StringEncoding {1760fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {1761match self {1762StringEncoding::Utf8 => fmt::Display::fmt(&"utf8", f),1763StringEncoding::Utf16 => fmt::Display::fmt(&"utf16", f),1764StringEncoding::Latin1OrUtf16 => fmt::Display::fmt(&"latin1+utf16", f),1765}1766}1767}17681769impl ToTokens for TestCaseOptions {1770fn to_tokens(&self, tokens: &mut TokenStream) {1771let TestCaseOptions {1772guest_caller_async,1773guest_callee_async,1774host_async,1775caller_encoding,1776callee_encoding,1777caller_lift_abi,1778callee_lift_abi,1779caller_lower_abi,1780callee_lower_abi,1781} = self;1782tokens.extend(quote!(wasmtime_test_util::component_fuzz::TestCaseOptions {1783guest_caller_async: #guest_caller_async,1784guest_callee_async: #guest_callee_async,1785host_async: #host_async,1786caller_encoding: #caller_encoding,1787callee_encoding: #callee_encoding,1788caller_lift_abi: #caller_lift_abi,1789callee_lift_abi: #callee_lift_abi,1790caller_lower_abi: #caller_lower_abi,1791callee_lower_abi: #callee_lower_abi,1792}));1793}1794}17951796impl ToTokens for LowerAbi {1797fn to_tokens(&self, tokens: &mut TokenStream) {1798let me = match self {1799LowerAbi::Sync => quote!(Sync),1800LowerAbi::Async => quote!(Async),1801};1802tokens.extend(quote!(wasmtime_test_util::component_fuzz::LowerAbi::#me));1803}1804}18051806impl ToTokens for LiftAbi {1807fn to_tokens(&self, tokens: &mut TokenStream) {1808let me = match self {1809LiftAbi::Sync => quote!(Sync),1810LiftAbi::AsyncCallback => quote!(AsyncCallback),1811LiftAbi::AsyncStackful => quote!(AsyncStackful),1812};1813tokens.extend(quote!(wasmtime_test_util::component_fuzz::LiftAbi::#me));1814}1815}18161817impl ToTokens for StringEncoding {1818fn to_tokens(&self, tokens: &mut TokenStream) {1819let me = match self {1820StringEncoding::Utf8 => quote!(Utf8),1821StringEncoding::Utf16 => quote!(Utf16),1822StringEncoding::Latin1OrUtf16 => quote!(Latin1OrUtf16),1823};1824tokens.extend(quote!(wasmtime_test_util::component_fuzz::StringEncoding::#me));1825}1826}18271828#[cfg(test)]1829mod tests {1830use super::*;18311832#[test]1833fn arbtest() {1834arbtest::arbtest(|u| {1835let mut fuel = 100;1836let types = (0..5)1837.map(|_| Type::generate(u, 3, &mut fuel))1838.collect::<arbitrary::Result<Vec<_>>>()?;1839let case = TestCase::generate(&types, u)?;1840let decls = case.declarations();1841let component = decls.make_component();1842let wasm = wat::parse_str(&component).unwrap_or_else(|e| {1843panic!("failed to parse generated component as wat: {e}\n\n{component}");1844});1845wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())1846.validate_all(&wasm)1847.unwrap_or_else(|e| {1848let mut wat = String::new();1849let mut dst = wasmprinter::PrintFmtWrite(&mut wat);1850let to_print = if wasmprinter::Config::new()1851.print_offsets(true)1852.print_operand_stack(true)1853.print(&wasm, &mut dst)1854.is_ok()1855{1856&wat[..]1857} else {1858&component[..]1859};1860panic!("generated component is not valid wasm: {e}\n\n{to_print}");1861});1862Ok(())1863})1864.budget_ms(1_000)1865// .seed(0x3c9050d4000000e9)1866;1867}1868}186918701871