1 //! This module generates test cases for the Wasmtime component model function APIs, 2 //! e.g. `wasmtime::component::func::Func` and `TypedFunc`. 3 //! 4 //! Each case includes a list of arbitrary interface types to use as parameters, plus another one to use as a 5 //! result, and a component which exports a function and imports a function. The exported function forwards its 6 //! parameters to the imported one and forwards the result back to the caller. This serves to exercise Wasmtime's 7 //! lifting and lowering code and verify the values remain intact during both processes. 8 9 use arbitrary::{Arbitrary, Unstructured}; 10 use proc_macro2::{Ident, TokenStream}; 11 use quote::{format_ident, quote, ToTokens}; 12 use std::borrow::Cow; 13 use std::fmt::{self, Debug, Write}; 14 use std::iter; 15 use std::ops::Deref; 16 use wasmtime_component_util::{DiscriminantSize, FlagsSize, REALLOC_AND_FREE}; 17 18 const MAX_FLAT_PARAMS: usize = 16; 19 const MAX_FLAT_RESULTS: usize = 1; 20 21 /// The name of the imported host function which the generated component will call 22 pub const IMPORT_FUNCTION: &str = "echo-import"; 23 24 /// The name of the exported guest function which the host should call 25 pub const EXPORT_FUNCTION: &str = "echo-export"; 26 27 /// Wasmtime allows up to 100 type depth so limit this to just under that. 28 pub const MAX_TYPE_DEPTH: u32 = 99; 29 30 #[derive(Copy, Clone, PartialEq, Eq)] 31 enum CoreType { 32 I32, 33 I64, 34 F32, 35 F64, 36 } 37 38 impl CoreType { 39 /// This is the `join` operation specified in [the canonical 40 /// ABI](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening) for 41 /// variant types. 42 fn join(self, other: Self) -> Self { 43 match (self, other) { 44 _ if self == other => self, 45 (Self::I32, Self::F32) | (Self::F32, Self::I32) => Self::I32, 46 _ => Self::I64, 47 } 48 } 49 } 50 51 impl fmt::Display for CoreType { 52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 match self { 54 Self::I32 => f.write_str("i32"), 55 Self::I64 => f.write_str("i64"), 56 Self::F32 => f.write_str("f32"), 57 Self::F64 => f.write_str("f64"), 58 } 59 } 60 } 61 62 /// Wraps a `Box<[T]>` and provides an `Arbitrary` implementation that always generates slices of length less than 63 /// or equal to the longest tuple for which Wasmtime generates a `ComponentType` impl 64 #[derive(Debug, Clone)] 65 pub struct VecInRange<T, const L: u32, const H: u32>(Vec<T>); 66 67 impl<T, const L: u32, const H: u32> VecInRange<T, L, H> { 68 fn new<'a>( 69 input: &mut Unstructured<'a>, 70 fuel: &mut u32, 71 generate: impl Fn(&mut Unstructured<'a>, &mut u32) -> arbitrary::Result<T>, 72 ) -> arbitrary::Result<Self> { 73 let mut ret = Vec::new(); 74 input.arbitrary_loop(Some(L), Some(H), |input| { 75 if *fuel > 0 { 76 *fuel = *fuel - 1; 77 ret.push(generate(input, fuel)?); 78 Ok(std::ops::ControlFlow::Continue(())) 79 } else { 80 Ok(std::ops::ControlFlow::Break(())) 81 } 82 })?; 83 Ok(Self(ret)) 84 } 85 } 86 87 impl<T, const L: u32, const H: u32> Deref for VecInRange<T, L, H> { 88 type Target = [T]; 89 90 fn deref(&self) -> &[T] { 91 self.0.deref() 92 } 93 } 94 95 /// Represents a component model interface type 96 #[expect(missing_docs, reason = "self-describing")] 97 #[derive(Debug, Clone)] 98 pub enum Type { 99 Bool, 100 S8, 101 U8, 102 S16, 103 U16, 104 S32, 105 U32, 106 S64, 107 U64, 108 Float32, 109 Float64, 110 Char, 111 String, 112 List(Box<Type>), 113 114 // Give records the ability to generate a generous amount of fields but 115 // don't let the fuzzer go too wild since `wasmparser`'s validator currently 116 // has hard limits in the 1000-ish range on the number of fields a record 117 // may contain. 118 Record(VecInRange<Type, 1, 200>), 119 120 // Tuples can only have up to 16 type parameters in wasmtime right now for 121 // the static API, but the standard library only supports `Debug` up to 11 122 // elements, so compromise at an even 10. 123 Tuple(VecInRange<Type, 1, 10>), 124 125 // Like records, allow a good number of variants, but variants require at 126 // least one case. 127 Variant(VecInRange<Option<Type>, 1, 200>), 128 Enum(u32), 129 130 Option(Box<Type>), 131 Result { 132 ok: Option<Box<Type>>, 133 err: Option<Box<Type>>, 134 }, 135 136 Flags(u32), 137 } 138 139 impl Type { 140 pub fn generate( 141 u: &mut Unstructured<'_>, 142 depth: u32, 143 fuel: &mut u32, 144 ) -> arbitrary::Result<Type> { 145 *fuel = fuel.saturating_sub(1); 146 let max = if depth == 0 || *fuel == 0 { 12 } else { 20 }; 147 Ok(match u.int_in_range(0..=max)? { 148 0 => Type::Bool, 149 1 => Type::S8, 150 2 => Type::U8, 151 3 => Type::S16, 152 4 => Type::U16, 153 5 => Type::S32, 154 6 => Type::U32, 155 7 => Type::S64, 156 8 => Type::U64, 157 9 => Type::Float32, 158 10 => Type::Float64, 159 11 => Type::Char, 160 12 => Type::String, 161 // ^-- if you add something here update the `depth == 0` case above 162 13 => Type::List(Box::new(Type::generate(u, depth - 1, fuel)?)), 163 14 => Type::Record(Type::generate_list(u, depth - 1, fuel)?), 164 15 => Type::Tuple(Type::generate_list(u, depth - 1, fuel)?), 165 16 => Type::Variant(VecInRange::new(u, fuel, |u, fuel| { 166 Type::generate_opt(u, depth - 1, fuel) 167 })?), 168 17 => { 169 let amt = u.int_in_range(1..=(*fuel).max(1).min(257))?; 170 *fuel -= amt; 171 Type::Enum(amt) 172 } 173 18 => Type::Option(Box::new(Type::generate(u, depth - 1, fuel)?)), 174 19 => Type::Result { 175 ok: Type::generate_opt(u, depth - 1, fuel)?.map(Box::new), 176 err: Type::generate_opt(u, depth - 1, fuel)?.map(Box::new), 177 }, 178 20 => { 179 let amt = u.int_in_range(1..=(*fuel).min(32))?; 180 *fuel -= amt; 181 Type::Flags(amt) 182 } 183 // ^-- if you add something here update the `depth != 0` case above 184 _ => unreachable!(), 185 }) 186 } 187 188 fn generate_opt( 189 u: &mut Unstructured<'_>, 190 depth: u32, 191 fuel: &mut u32, 192 ) -> arbitrary::Result<Option<Type>> { 193 Ok(if u.arbitrary()? { 194 Some(Type::generate(u, depth, fuel)?) 195 } else { 196 None 197 }) 198 } 199 200 fn generate_list<const L: u32, const H: u32>( 201 u: &mut Unstructured<'_>, 202 depth: u32, 203 fuel: &mut u32, 204 ) -> arbitrary::Result<VecInRange<Type, L, H>> { 205 VecInRange::new(u, fuel, |u, fuel| Type::generate(u, depth, fuel)) 206 } 207 } 208 209 fn lower_record<'a>(types: impl Iterator<Item = &'a Type>, vec: &mut Vec<CoreType>) { 210 for ty in types { 211 ty.lower(vec); 212 } 213 } 214 215 fn lower_variant<'a>(types: impl Iterator<Item = Option<&'a Type>>, vec: &mut Vec<CoreType>) { 216 vec.push(CoreType::I32); 217 let offset = vec.len(); 218 for ty in types { 219 let ty = match ty { 220 Some(ty) => ty, 221 None => continue, 222 }; 223 for (index, ty) in ty.lowered().iter().enumerate() { 224 let index = offset + index; 225 if index < vec.len() { 226 vec[index] = vec[index].join(*ty); 227 } else { 228 vec.push(*ty) 229 } 230 } 231 } 232 } 233 234 fn u32_count_from_flag_count(count: usize) -> usize { 235 match FlagsSize::from_count(count) { 236 FlagsSize::Size0 => 0, 237 FlagsSize::Size1 | FlagsSize::Size2 => 1, 238 FlagsSize::Size4Plus(n) => n.into(), 239 } 240 } 241 242 struct SizeAndAlignment { 243 size: usize, 244 alignment: u32, 245 } 246 247 impl Type { 248 fn lowered(&self) -> Vec<CoreType> { 249 let mut vec = Vec::new(); 250 self.lower(&mut vec); 251 vec 252 } 253 254 fn lower(&self, vec: &mut Vec<CoreType>) { 255 match self { 256 Type::Bool 257 | Type::U8 258 | Type::S8 259 | Type::S16 260 | Type::U16 261 | Type::S32 262 | Type::U32 263 | Type::Char 264 | Type::Enum(_) => vec.push(CoreType::I32), 265 Type::S64 | Type::U64 => vec.push(CoreType::I64), 266 Type::Float32 => vec.push(CoreType::F32), 267 Type::Float64 => vec.push(CoreType::F64), 268 Type::String | Type::List(_) => { 269 vec.push(CoreType::I32); 270 vec.push(CoreType::I32); 271 } 272 Type::Record(types) => lower_record(types.iter(), vec), 273 Type::Tuple(types) => lower_record(types.0.iter(), vec), 274 Type::Variant(types) => lower_variant(types.0.iter().map(|t| t.as_ref()), vec), 275 Type::Option(ty) => lower_variant([None, Some(&**ty)].into_iter(), vec), 276 Type::Result { ok, err } => { 277 lower_variant([ok.as_deref(), err.as_deref()].into_iter(), vec) 278 } 279 Type::Flags(count) => vec.extend( 280 iter::repeat(CoreType::I32).take(u32_count_from_flag_count(*count as usize)), 281 ), 282 } 283 } 284 285 fn size_and_alignment(&self) -> SizeAndAlignment { 286 match self { 287 Type::Bool | Type::S8 | Type::U8 => SizeAndAlignment { 288 size: 1, 289 alignment: 1, 290 }, 291 292 Type::S16 | Type::U16 => SizeAndAlignment { 293 size: 2, 294 alignment: 2, 295 }, 296 297 Type::S32 | Type::U32 | Type::Char | Type::Float32 => SizeAndAlignment { 298 size: 4, 299 alignment: 4, 300 }, 301 302 Type::S64 | Type::U64 | Type::Float64 => SizeAndAlignment { 303 size: 8, 304 alignment: 8, 305 }, 306 307 Type::String | Type::List(_) => SizeAndAlignment { 308 size: 8, 309 alignment: 4, 310 }, 311 312 Type::Record(types) => record_size_and_alignment(types.iter()), 313 314 Type::Tuple(types) => record_size_and_alignment(types.0.iter()), 315 316 Type::Variant(types) => variant_size_and_alignment(types.0.iter().map(|t| t.as_ref())), 317 318 Type::Enum(count) => variant_size_and_alignment((0..*count).map(|_| None)), 319 320 Type::Option(ty) => variant_size_and_alignment([None, Some(&**ty)].into_iter()), 321 322 Type::Result { ok, err } => { 323 variant_size_and_alignment([ok.as_deref(), err.as_deref()].into_iter()) 324 } 325 326 Type::Flags(count) => match FlagsSize::from_count(*count as usize) { 327 FlagsSize::Size0 => SizeAndAlignment { 328 size: 0, 329 alignment: 1, 330 }, 331 FlagsSize::Size1 => SizeAndAlignment { 332 size: 1, 333 alignment: 1, 334 }, 335 FlagsSize::Size2 => SizeAndAlignment { 336 size: 2, 337 alignment: 2, 338 }, 339 FlagsSize::Size4Plus(n) => SizeAndAlignment { 340 size: usize::from(n) * 4, 341 alignment: 4, 342 }, 343 }, 344 } 345 } 346 } 347 348 fn align_to(a: usize, align: u32) -> usize { 349 let align = align as usize; 350 (a + (align - 1)) & !(align - 1) 351 } 352 353 fn record_size_and_alignment<'a>(types: impl Iterator<Item = &'a Type>) -> SizeAndAlignment { 354 let mut offset = 0; 355 let mut align = 1; 356 for ty in types { 357 let SizeAndAlignment { size, alignment } = ty.size_and_alignment(); 358 offset = align_to(offset, alignment) + size; 359 align = align.max(alignment); 360 } 361 362 SizeAndAlignment { 363 size: align_to(offset, align), 364 alignment: align, 365 } 366 } 367 368 fn variant_size_and_alignment<'a>( 369 types: impl ExactSizeIterator<Item = Option<&'a Type>>, 370 ) -> SizeAndAlignment { 371 let discriminant_size = DiscriminantSize::from_count(types.len()).unwrap(); 372 let mut alignment = u32::from(discriminant_size); 373 let mut size = 0; 374 for ty in types { 375 if let Some(ty) = ty { 376 let size_and_alignment = ty.size_and_alignment(); 377 alignment = alignment.max(size_and_alignment.alignment); 378 size = size.max(size_and_alignment.size); 379 } 380 } 381 382 SizeAndAlignment { 383 size: align_to( 384 align_to(usize::from(discriminant_size), alignment) + size, 385 alignment, 386 ), 387 alignment, 388 } 389 } 390 391 fn make_import_and_export(params: &[&Type], result: Option<&Type>) -> String { 392 let params_lowered = params 393 .iter() 394 .flat_map(|ty| ty.lowered()) 395 .collect::<Box<[_]>>(); 396 let result_lowered = result.map(|t| t.lowered()).unwrap_or(Vec::new()); 397 398 let mut core_params = String::new(); 399 let mut gets = String::new(); 400 401 if params_lowered.len() <= MAX_FLAT_PARAMS { 402 for (index, param) in params_lowered.iter().enumerate() { 403 write!(&mut core_params, " {param}").unwrap(); 404 write!(&mut gets, "local.get {index} ").unwrap(); 405 } 406 } else { 407 write!(&mut core_params, " i32").unwrap(); 408 write!(&mut gets, "local.get 0 ").unwrap(); 409 } 410 411 let maybe_core_params = if params_lowered.is_empty() { 412 String::new() 413 } else { 414 format!("(param{core_params})") 415 }; 416 417 if result_lowered.len() <= MAX_FLAT_RESULTS { 418 let mut core_results = String::new(); 419 for result in result_lowered.iter() { 420 write!(&mut core_results, " {result}").unwrap(); 421 } 422 423 let maybe_core_results = if result_lowered.is_empty() { 424 String::new() 425 } else { 426 format!("(result{core_results})") 427 }; 428 429 format!( 430 r#" 431 (func $f (import "host" "{IMPORT_FUNCTION}") {maybe_core_params} {maybe_core_results}) 432 433 (func (export "{EXPORT_FUNCTION}") {maybe_core_params} {maybe_core_results} 434 {gets} 435 436 call $f 437 )"# 438 ) 439 } else { 440 let SizeAndAlignment { size, alignment } = result.unwrap().size_and_alignment(); 441 442 format!( 443 r#" 444 (func $f (import "host" "{IMPORT_FUNCTION}") (param{core_params} i32)) 445 446 (func (export "{EXPORT_FUNCTION}") {maybe_core_params} (result i32) 447 (local $base i32) 448 (local.set $base 449 (call $realloc 450 (i32.const 0) 451 (i32.const 0) 452 (i32.const {alignment}) 453 (i32.const {size}))) 454 {gets} 455 local.get $base 456 457 call $f 458 459 local.get $base 460 )"# 461 ) 462 } 463 } 464 465 fn make_rust_name(name_counter: &mut u32) -> Ident { 466 let name = format_ident!("Foo{name_counter}"); 467 *name_counter += 1; 468 name 469 } 470 471 /// Generate a [`TokenStream`] containing the rust type name for a type. 472 /// 473 /// The `name_counter` parameter is used to generate names for each recursively visited type. The `declarations` 474 /// parameter is used to accumulate declarations for each recursively visited type. 475 pub fn rust_type(ty: &Type, name_counter: &mut u32, declarations: &mut TokenStream) -> TokenStream { 476 match ty { 477 Type::Bool => quote!(bool), 478 Type::S8 => quote!(i8), 479 Type::U8 => quote!(u8), 480 Type::S16 => quote!(i16), 481 Type::U16 => quote!(u16), 482 Type::S32 => quote!(i32), 483 Type::U32 => quote!(u32), 484 Type::S64 => quote!(i64), 485 Type::U64 => quote!(u64), 486 Type::Float32 => quote!(Float32), 487 Type::Float64 => quote!(Float64), 488 Type::Char => quote!(char), 489 Type::String => quote!(Box<str>), 490 Type::List(ty) => { 491 let ty = rust_type(ty, name_counter, declarations); 492 quote!(Vec<#ty>) 493 } 494 Type::Record(types) => { 495 let fields = types 496 .iter() 497 .enumerate() 498 .map(|(index, ty)| { 499 let name = format_ident!("f{index}"); 500 let ty = rust_type(ty, name_counter, declarations); 501 quote!(#name: #ty,) 502 }) 503 .collect::<TokenStream>(); 504 505 let name = make_rust_name(name_counter); 506 507 declarations.extend(quote! { 508 #[derive(ComponentType, Lift, Lower, PartialEq, Debug, Clone, Arbitrary)] 509 #[component(record)] 510 struct #name { 511 #fields 512 } 513 }); 514 515 quote!(#name) 516 } 517 Type::Tuple(types) => { 518 let fields = types 519 .0 520 .iter() 521 .map(|ty| { 522 let ty = rust_type(ty, name_counter, declarations); 523 quote!(#ty,) 524 }) 525 .collect::<TokenStream>(); 526 527 quote!((#fields)) 528 } 529 Type::Variant(types) => { 530 let cases = types 531 .0 532 .iter() 533 .enumerate() 534 .map(|(index, ty)| { 535 let name = format_ident!("C{index}"); 536 let ty = match ty { 537 Some(ty) => { 538 let ty = rust_type(ty, name_counter, declarations); 539 quote!((#ty)) 540 } 541 None => quote!(), 542 }; 543 quote!(#name #ty,) 544 }) 545 .collect::<TokenStream>(); 546 547 let name = make_rust_name(name_counter); 548 declarations.extend(quote! { 549 #[derive(ComponentType, Lift, Lower, PartialEq, Debug, Clone, Arbitrary)] 550 #[component(variant)] 551 enum #name { 552 #cases 553 } 554 }); 555 556 quote!(#name) 557 } 558 Type::Enum(count) => { 559 let cases = (0..*count) 560 .map(|index| { 561 let name = format_ident!("E{index}"); 562 quote!(#name,) 563 }) 564 .collect::<TokenStream>(); 565 566 let name = make_rust_name(name_counter); 567 let repr = match count.ilog2() { 568 0..=7 => quote!(u8), 569 8..=15 => quote!(u16), 570 _ => quote!(u32), 571 }; 572 573 declarations.extend(quote! { 574 #[derive(ComponentType, Lift, Lower, PartialEq, Debug, Copy, Clone, Arbitrary)] 575 #[component(enum)] 576 #[repr(#repr)] 577 enum #name { 578 #cases 579 } 580 }); 581 582 quote!(#name) 583 } 584 Type::Option(ty) => { 585 let ty = rust_type(ty, name_counter, declarations); 586 quote!(Option<#ty>) 587 } 588 Type::Result { ok, err } => { 589 let ok = match ok { 590 Some(ok) => rust_type(ok, name_counter, declarations), 591 None => quote!(()), 592 }; 593 let err = match err { 594 Some(err) => rust_type(err, name_counter, declarations), 595 None => quote!(()), 596 }; 597 quote!(Result<#ok, #err>) 598 } 599 Type::Flags(count) => { 600 let type_name = make_rust_name(name_counter); 601 602 let mut flags = TokenStream::new(); 603 let mut names = TokenStream::new(); 604 605 for index in 0..*count { 606 let name = format_ident!("F{index}"); 607 flags.extend(quote!(const #name;)); 608 names.extend(quote!(#type_name::#name,)) 609 } 610 611 declarations.extend(quote! { 612 wasmtime::component::flags! { 613 #type_name { 614 #flags 615 } 616 } 617 618 impl<'a> arbitrary::Arbitrary<'a> for #type_name { 619 fn arbitrary(input: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { 620 let mut flags = #type_name::default(); 621 for flag in [#names] { 622 if input.arbitrary()? { 623 flags |= flag; 624 } 625 } 626 Ok(flags) 627 } 628 } 629 }); 630 631 quote!(#type_name) 632 } 633 } 634 } 635 636 #[derive(Default)] 637 struct TypesBuilder<'a> { 638 next: u32, 639 worklist: Vec<(u32, &'a Type)>, 640 } 641 642 impl<'a> TypesBuilder<'a> { 643 fn write_ref(&mut self, ty: &'a Type, dst: &mut String) { 644 match ty { 645 // Primitive types can be referenced directly 646 Type::Bool => dst.push_str("bool"), 647 Type::S8 => dst.push_str("s8"), 648 Type::U8 => dst.push_str("u8"), 649 Type::S16 => dst.push_str("s16"), 650 Type::U16 => dst.push_str("u16"), 651 Type::S32 => dst.push_str("s32"), 652 Type::U32 => dst.push_str("u32"), 653 Type::S64 => dst.push_str("s64"), 654 Type::U64 => dst.push_str("u64"), 655 Type::Float32 => dst.push_str("float32"), 656 Type::Float64 => dst.push_str("float64"), 657 Type::Char => dst.push_str("char"), 658 Type::String => dst.push_str("string"), 659 660 // Otherwise emit a reference to the type and remember to generate 661 // the corresponding type alias later. 662 Type::List(_) 663 | Type::Record(_) 664 | Type::Tuple(_) 665 | Type::Variant(_) 666 | Type::Enum(_) 667 | Type::Option(_) 668 | Type::Result { .. } 669 | Type::Flags(_) => { 670 let idx = self.next; 671 self.next += 1; 672 write!(dst, "$t{idx}").unwrap(); 673 self.worklist.push((idx, ty)); 674 } 675 } 676 } 677 678 fn write_decl(&mut self, idx: u32, ty: &'a Type) -> String { 679 let mut decl = format!("(type $t{idx}' "); 680 match ty { 681 Type::Bool 682 | Type::S8 683 | Type::U8 684 | Type::S16 685 | Type::U16 686 | Type::S32 687 | Type::U32 688 | Type::S64 689 | Type::U64 690 | Type::Float32 691 | Type::Float64 692 | Type::Char 693 | Type::String => unreachable!(), 694 695 Type::List(ty) => { 696 decl.push_str("(list "); 697 self.write_ref(ty, &mut decl); 698 decl.push_str(")"); 699 } 700 Type::Record(types) => { 701 decl.push_str("(record"); 702 for (index, ty) in types.iter().enumerate() { 703 write!(decl, r#" (field "f{index}" "#).unwrap(); 704 self.write_ref(ty, &mut decl); 705 decl.push_str(")"); 706 } 707 decl.push_str(")"); 708 } 709 Type::Tuple(types) => { 710 decl.push_str("(tuple"); 711 for ty in types.iter() { 712 decl.push_str(" "); 713 self.write_ref(ty, &mut decl); 714 } 715 decl.push_str(")"); 716 } 717 Type::Variant(types) => { 718 decl.push_str("(variant"); 719 for (index, ty) in types.iter().enumerate() { 720 write!(decl, r#" (case "C{index}""#).unwrap(); 721 if let Some(ty) = ty { 722 decl.push_str(" "); 723 self.write_ref(ty, &mut decl); 724 } 725 decl.push_str(")"); 726 } 727 decl.push_str(")"); 728 } 729 Type::Enum(count) => { 730 decl.push_str("(enum"); 731 for index in 0..*count { 732 write!(decl, r#" "E{index}""#).unwrap(); 733 } 734 decl.push_str(")"); 735 } 736 Type::Option(ty) => { 737 decl.push_str("(option "); 738 self.write_ref(ty, &mut decl); 739 decl.push_str(")"); 740 } 741 Type::Result { ok, err } => { 742 decl.push_str("(result"); 743 if let Some(ok) = ok { 744 decl.push_str(" "); 745 self.write_ref(ok, &mut decl); 746 } 747 if let Some(err) = err { 748 decl.push_str(" (error "); 749 self.write_ref(err, &mut decl); 750 decl.push_str(")"); 751 } 752 decl.push_str(")"); 753 } 754 Type::Flags(count) => { 755 decl.push_str("(flags"); 756 for index in 0..*count { 757 write!(decl, r#" "F{index}""#).unwrap(); 758 } 759 decl.push_str(")"); 760 } 761 } 762 decl.push_str(")\n"); 763 writeln!(decl, "(import \"t{idx}\" (type $t{idx} (eq $t{idx}')))").unwrap(); 764 decl 765 } 766 } 767 768 /// Represents custom fragments of a WAT file which may be used to create a component for exercising [`TestCase`]s 769 #[derive(Debug)] 770 pub struct Declarations { 771 /// Type declarations (if any) referenced by `params` and/or `result` 772 pub types: Cow<'static, str>, 773 /// Types to thread through when instantiating sub-components. 774 pub type_instantiation_args: Cow<'static, str>, 775 /// Parameter declarations used for the imported and exported functions 776 pub params: Cow<'static, str>, 777 /// Result declaration used for the imported and exported functions 778 pub results: Cow<'static, str>, 779 /// A WAT fragment representing the core function import and export to use for testing 780 pub import_and_export: Cow<'static, str>, 781 /// String encoding to use for host -> component 782 pub encoding1: StringEncoding, 783 /// String encoding to use for component -> host 784 pub encoding2: StringEncoding, 785 } 786 787 impl Declarations { 788 /// Generate a complete WAT file based on the specified fragments. 789 pub fn make_component(&self) -> Box<str> { 790 let Self { 791 types, 792 type_instantiation_args, 793 params, 794 results, 795 import_and_export, 796 encoding1, 797 encoding2, 798 } = self; 799 let mk_component = |name: &str, encoding: StringEncoding| { 800 format!( 801 r#" 802 (component ${name} 803 {types} 804 (type $sig (func {params} {results})) 805 (import "{IMPORT_FUNCTION}" (func $f (type $sig))) 806 807 (core instance $libc (instantiate $libc)) 808 809 (core func $f_lower (canon lower 810 (func $f) 811 (memory $libc "memory") 812 (realloc (func $libc "realloc")) 813 string-encoding={encoding} 814 )) 815 816 (core instance $i (instantiate $m 817 (with "libc" (instance $libc)) 818 (with "host" (instance (export "{IMPORT_FUNCTION}" (func $f_lower)))) 819 )) 820 821 (func (export "{EXPORT_FUNCTION}") (type $sig) 822 (canon lift 823 (core func $i "{EXPORT_FUNCTION}") 824 (memory $libc "memory") 825 (realloc (func $libc "realloc")) 826 string-encoding={encoding} 827 ) 828 ) 829 ) 830 "# 831 ) 832 }; 833 834 let c1 = mk_component("c1", *encoding2); 835 let c2 = mk_component("c2", *encoding1); 836 837 format!( 838 r#" 839 (component 840 (core module $libc 841 (memory (export "memory") 1) 842 {REALLOC_AND_FREE} 843 ) 844 845 (core module $m 846 (memory (import "libc" "memory") 1) 847 (func $realloc (import "libc" "realloc") (param i32 i32 i32 i32) (result i32)) 848 849 {import_and_export} 850 ) 851 852 {types} 853 854 (type $sig (func {params} {results})) 855 (import "{IMPORT_FUNCTION}" (func $f (type $sig))) 856 857 {c1} 858 {c2} 859 (instance $c1 (instantiate $c1 860 {type_instantiation_args} 861 (with "{IMPORT_FUNCTION}" (func $f)) 862 )) 863 (instance $c2 (instantiate $c2 864 {type_instantiation_args} 865 (with "{IMPORT_FUNCTION}" (func $c1 "{EXPORT_FUNCTION}")) 866 )) 867 (export "{EXPORT_FUNCTION}" (func $c2 "{EXPORT_FUNCTION}")) 868 )"#, 869 ) 870 .into() 871 } 872 } 873 874 /// Represents a test case for calling a component function 875 #[derive(Debug)] 876 pub struct TestCase<'a> { 877 /// The types of parameters to pass to the function 878 pub params: Vec<&'a Type>, 879 /// The result types of the function 880 pub result: Option<&'a Type>, 881 /// String encoding to use from host-to-component. 882 pub encoding1: StringEncoding, 883 /// String encoding to use from component-to-host. 884 pub encoding2: StringEncoding, 885 } 886 887 impl TestCase<'_> { 888 /// Generate a `Declarations` for this `TestCase` which may be used to build a component to execute the case. 889 pub fn declarations(&self) -> Declarations { 890 let mut builder = TypesBuilder::default(); 891 892 let mut params = String::new(); 893 for (i, ty) in self.params.iter().enumerate() { 894 params.push_str(&format!(" (param \"p{i}\" ")); 895 builder.write_ref(ty, &mut params); 896 params.push_str(")"); 897 } 898 899 let mut results = String::new(); 900 if let Some(ty) = self.result { 901 results.push_str(&format!(" (result ")); 902 builder.write_ref(ty, &mut results); 903 results.push_str(")"); 904 } 905 906 let import_and_export = make_import_and_export(&self.params, self.result); 907 908 let mut type_decls = Vec::new(); 909 let mut type_instantiation_args = String::new(); 910 while let Some((idx, ty)) = builder.worklist.pop() { 911 type_decls.push(builder.write_decl(idx, ty)); 912 writeln!(type_instantiation_args, "(with \"t{idx}\" (type $t{idx}))").unwrap(); 913 } 914 915 // Note that types are printed here in reverse order since they were 916 // pushed onto `type_decls` as they were referenced meaning the last one 917 // is the "base" one. 918 let mut types = String::new(); 919 for decl in type_decls.into_iter().rev() { 920 types.push_str(&decl); 921 types.push_str("\n"); 922 } 923 924 Declarations { 925 types: types.into(), 926 type_instantiation_args: type_instantiation_args.into(), 927 params: params.into(), 928 results: results.into(), 929 import_and_export: import_and_export.into(), 930 encoding1: self.encoding1, 931 encoding2: self.encoding2, 932 } 933 } 934 } 935 936 #[derive(Copy, Clone, Debug, Arbitrary)] 937 pub enum StringEncoding { 938 Utf8, 939 Utf16, 940 Latin1OrUtf16, 941 } 942 943 impl fmt::Display for StringEncoding { 944 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 945 match self { 946 StringEncoding::Utf8 => fmt::Display::fmt(&"utf8", f), 947 StringEncoding::Utf16 => fmt::Display::fmt(&"utf16", f), 948 StringEncoding::Latin1OrUtf16 => fmt::Display::fmt(&"latin1+utf16", f), 949 } 950 } 951 } 952 953 impl ToTokens for StringEncoding { 954 fn to_tokens(&self, tokens: &mut TokenStream) { 955 let me = match self { 956 StringEncoding::Utf8 => quote!(Utf8), 957 StringEncoding::Utf16 => quote!(Utf16), 958 StringEncoding::Latin1OrUtf16 => quote!(Latin1OrUtf16), 959 }; 960 tokens.extend(quote!(wasmtime_test_util::component_fuzz::StringEncoding::#me)); 961 } 962 } 963