From d498715b315bfe0d96ac89d8de6f36cf92ae62d1 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 30 Oct 2024 14:13:48 -0700 Subject: [PATCH] threads: fuzz `shared-everything-threads` (#1756) * threads: fuzz `shared-everything-threads` This adds initial support for fuzzing the WebAssembly side of the shared-everything-threads [proposal]. Eventual addition of the CM builtins will require extensions to this. [proposal]: https://github.com/WebAssembly/shared-everything-threads * Propagate shared function type into the code builder * Avoid shared function bodies for now Since it may take a while to implement all the "reverse validation" in wasm-smith's instruction generator, this effectively cordons off that bit of the fuzzer so that we can at least generate shared types in modules. Later, once the instruction generator is smarter, we will remove this filter and start generating shared function bodies. * Avoid shared const expressions for now Like shared function bodies, this can be cordoned off until later. * Allow `CodeBuilder::shared` to be unused for now * Check sharedness in `arbitrary_super_type_of_heap_type` * Actually filter `ref.func` const expressions In 2a2ddd5748, I tried to avoid `ref.func` expressions entirely for shared types but unfortunately the other side could still occur: a `ref.func` on an unshared table that generates a shared funcref. This filters out the available types to match sharedness. * review: move feature-flipping to `Config::sanitize` * review: fuzz shared-everything at the same ratio as threads * fix: disable shared-everything if reference types is disabled * fix: disable shared-everything entirely for mutate fuzz target * review: invisibly propagate sharedness As @alexcrichton suggested, this holds a `must_share` flag on `Module` and carefully sets it and unsets it using `propagate_shared`. * fix: formatting * review: revert `arbitrary_ref_type` changes * review: add TODO to remove extra check * review: use `arbitrary_shared` more --- crates/wasm-encoder/src/core/types.rs | 8 + crates/wasm-smith/src/config.rs | 31 ++- crates/wasm-smith/src/core.rs | 294 +++++++++++++++------ crates/wasm-smith/src/core/code_builder.rs | 43 +-- fuzz/src/lib.rs | 1 + fuzz/src/mutate.rs | 6 +- 6 files changed, 272 insertions(+), 111 deletions(-) diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs index 5cc3bdf502..5dda818f37 100644 --- a/crates/wasm-encoder/src/core/types.rs +++ b/crates/wasm-encoder/src/core/types.rs @@ -293,6 +293,14 @@ impl RefType { }, }; + /// Create a new abstract reference type. + pub fn new_abstract(ty: AbstractHeapType, nullable: bool, shared: bool) -> Self { + Self { + nullable, + heap_type: HeapType::Abstract { shared, ty }, + } + } + /// Set the nullability of this reference type. pub fn nullable(mut self, nullable: bool) -> Self { self.nullable = nullable; diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs index 7702a170e8..42cd8102e1 100644 --- a/crates/wasm-smith/src/config.rs +++ b/crates/wasm-smith/src/config.rs @@ -551,17 +551,30 @@ define_config! { /// Defaults to `true`. pub relaxed_simd_enabled: bool = true, - /// Determines whether the nontrapping-float-to-int-conversions propsal - /// is enabled. + /// Determines whether the non-trapping float-to-int conversions + /// proposal is enabled. /// /// Defaults to `true`. pub saturating_float_to_int_enabled: bool = true, - /// Determines whether the sign-extension-ops propsal is enabled. + /// Determines whether the sign-extension-ops proposal is enabled. /// /// Defaults to `true`. pub sign_extension_ops_enabled: bool = true, + /// Determines whether the shared-everything-threads proposal is + /// enabled. + /// + /// The [shared-everything-threads] proposal, among other things, + /// extends `shared` attributes to all WebAssembly objects; it builds on + /// the [threads] proposal. + /// + /// [shared-everything-threads]: https://github.com/WebAssembly/shared-everything-threads + /// [threads]: https://github.com/WebAssembly/threads + /// + /// Defaults to `false`. + pub shared_everything_threads_enabled: bool = false, + /// Determines whether the SIMD proposal is enabled for generating /// instructions. /// @@ -754,6 +767,7 @@ impl<'a> Arbitrary<'a> for Config { memory64_enabled: false, custom_page_sizes_enabled: false, wide_arithmetic_enabled: false, + shared_everything_threads_enabled: false, }; config.sanitize(); Ok(config) @@ -779,6 +793,7 @@ impl Config { if !self.reference_types_enabled { self.max_tables = self.max_tables.min(1); self.gc_enabled = false; + self.shared_everything_threads_enabled = false; } // If simd is disabled then disable all relaxed simd instructions as @@ -786,6 +801,12 @@ impl Config { if !self.simd_enabled { self.relaxed_simd_enabled = false; } + + // It is impossible to use the shared-everything-threads proposal + // without threads, which it is built on. + if !self.threads_enabled { + self.shared_everything_threads_enabled = false; + } } /// Returns the set of features that are necessary for validating against @@ -821,6 +842,10 @@ impl Config { features.set(WasmFeatures::FUNCTION_REFERENCES, self.gc_enabled); features.set(WasmFeatures::GC, self.gc_enabled); features.set(WasmFeatures::THREADS, self.threads_enabled); + features.set( + WasmFeatures::SHARED_EVERYTHING_THREADS, + self.shared_everything_threads_enabled, + ); features.set( WasmFeatures::CUSTOM_PAGE_SIZES, self.custom_page_sizes_enabled, diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index b5430b09a2..d9d406600d 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -1,4 +1,4 @@ -//! Generating arbitary core Wasm modules. +//! Generating arbitrary core Wasm modules. mod code_builder; pub(crate) mod encode; @@ -66,6 +66,10 @@ pub struct Module { /// Whether we should encode a types section, even if `self.types` is empty. should_encode_types: bool, + /// Whether we should propagate sharedness to types generated inside + /// `propagate_shared`. + must_share: bool, + /// All of this module's imports. These don't have their own index space, /// but instead introduce entries to each imported entity's associated index /// space. @@ -246,6 +250,7 @@ impl Module { max_type_limit: MaxTypeLimit::ModuleTypes, interesting_values32: Vec::new(), interesting_values64: Vec::new(), + must_share: false, } } } @@ -516,7 +521,7 @@ impl Module { (HT::Concrete(a), HT::Abstract { shared, ty }) => { let a_ty = &self.ty(a).composite_type; - if a_ty.shared == shared { + if a_ty.shared != shared { return false; } match ty { @@ -530,7 +535,7 @@ impl Module { (HT::Abstract { shared, ty }, HT::Concrete(b)) => { let b_ty = &self.ty(b).composite_type; - if shared == b_ty.shared { + if shared != b_ty.shared { return false; } match ty { @@ -572,6 +577,7 @@ impl Module { let index = u32::try_from(self.types.len()).unwrap(); if let Some(supertype) = ty.supertype { + assert_eq!(self.is_shared_type(supertype), ty.composite_type.shared); self.super_to_sub_types .entry(supertype) .or_default() @@ -658,12 +664,12 @@ impl Module { fn clone_rec_group(&mut self, u: &mut Unstructured, kind: AllowEmptyRecGroup) -> Result<()> { // NB: this does *not* guarantee that the cloned rec group will - // canonicalize the same as the original rec group and be - // deduplicated. That would reqiure a second pass over the cloned types - // to rewrite references within the original rec group to be references - // into the new rec group. That might make sense to do one day, but for - // now we don't do it. That also means that we can't mark the new types - // as "subtypes" of the old types and vice versa. + // canonicalize the same as the original rec group and be deduplicated. + // That would require a second pass over the cloned types to rewrite + // references within the original rec group to be references into the + // new rec group. That might make sense to do one day, but for now we + // don't do it. That also means that we can't mark the new types as + // "subtypes" of the old types and vice versa. let candidates: Vec<_> = self.clonable_rec_groups(kind).collect(); let group = u.choose(&candidates)?.clone(); let new_rec_group_start = self.types.len(); @@ -678,9 +684,11 @@ impl Module { fn arbitrary_sub_type(&mut self, u: &mut Unstructured) -> Result { if !self.config.gc_enabled { + let shared = self.arbitrary_shared(u)?; + let func_type = self.propagate_shared(shared, |m| m.arbitrary_func_type(u))?; let composite_type = CompositeType { - inner: CompositeInnerType::Func(self.arbitrary_func_type(u)?), - shared: false, + inner: CompositeInnerType::Func(func_type), + shared, }; return Ok(SubType { is_final: true, @@ -713,7 +721,9 @@ impl Module { *f = self.arbitrary_matching_func_type(u, f)?; } CompositeInnerType::Struct(s) => { - *s = self.arbitrary_matching_struct_type(u, s)?; + *s = self.propagate_shared(composite_type.shared, |m| { + m.arbitrary_matching_struct_type(u, s) + })?; } } Ok(SubType { @@ -787,44 +797,55 @@ impl Module { } fn arbitrary_matching_heap_type(&self, u: &mut Unstructured, ty: HeapType) -> Result { + use {AbstractHeapType as AHT, CompositeInnerType as CT, HeapType as HT}; + if !self.config.gc_enabled { return Ok(ty); } - use CompositeInnerType as CT; - use HeapType as HT; + let mut choices = vec![ty]; match ty { HT::Abstract { shared, ty } => { use AbstractHeapType::*; - let ht = |ty| HT::Abstract { shared, ty }; + let add_abstract = |choices: &mut Vec, tys: &[AHT]| { + choices.extend(tys.iter().map(|&ty| HT::Abstract { shared, ty })); + }; + let add_concrete = |choices: &mut Vec, tys: &[u32]| { + choices.extend( + tys.iter() + .filter(|&&idx| shared == self.is_shared_type(idx)) + .copied() + .map(HT::Concrete), + ); + }; match ty { Any => { - choices.extend([ht(Eq), ht(Struct), ht(Array), ht(I31), ht(None)]); - choices.extend(self.array_types.iter().copied().map(HT::Concrete)); - choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); + add_abstract(&mut choices, &[Eq, Struct, Array, I31, None]); + add_concrete(&mut choices, &self.array_types); + add_concrete(&mut choices, &self.struct_types); } Eq => { - choices.extend([ht(Struct), ht(Array), ht(I31), ht(None)]); - choices.extend(self.array_types.iter().copied().map(HT::Concrete)); - choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); + add_abstract(&mut choices, &[Struct, Array, I31, None]); + add_concrete(&mut choices, &self.array_types); + add_concrete(&mut choices, &self.struct_types); } Struct => { - choices.extend([ht(Struct), ht(None)]); - choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); + add_abstract(&mut choices, &[Struct, None]); + add_concrete(&mut choices, &self.struct_types); } Array => { - choices.extend([ht(Array), ht(None)]); - choices.extend(self.array_types.iter().copied().map(HT::Concrete)); + add_abstract(&mut choices, &[Array, None]); + add_concrete(&mut choices, &self.array_types); } I31 => { - choices.push(ht(None)); + add_abstract(&mut choices, &[None]); } Func => { - choices.extend(self.func_types.iter().copied().map(HT::Concrete)); - choices.push(ht(NoFunc)); + add_abstract(&mut choices, &[NoFunc]); + add_concrete(&mut choices, &self.func_types); } Extern => { - choices.push(ht(NoExtern)); + add_abstract(&mut choices, &[NoExtern]); } Exn | NoExn | None | NoExtern | NoFunc | Cont | NoCont => {} } @@ -915,67 +936,71 @@ impl Module { u: &mut Unstructured, ty: HeapType, ) -> Result { + use {AbstractHeapType as AHT, CompositeInnerType as CT, HeapType as HT}; + if !self.config.gc_enabled { return Ok(ty); } - use CompositeInnerType as CT; - use HeapType as HT; + let mut choices = vec![ty]; match ty { HT::Abstract { shared, ty } => { use AbstractHeapType::*; - let ht = |ty| HT::Abstract { shared, ty }; + let add_abstract = |choices: &mut Vec, tys: &[AHT]| { + choices.extend(tys.iter().map(|&ty| HT::Abstract { shared, ty })); + }; + let add_concrete = |choices: &mut Vec, tys: &[u32]| { + choices.extend( + tys.iter() + .filter(|&&idx| shared == self.is_shared_type(idx)) + .copied() + .map(HT::Concrete), + ); + }; match ty { None => { - choices.extend([ht(Any), ht(Eq), ht(Struct), ht(Array), ht(I31)]); - choices.extend(self.array_types.iter().copied().map(HT::Concrete)); - choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); + add_abstract(&mut choices, &[Any, Eq, Struct, Array, I31]); + add_concrete(&mut choices, &self.array_types); + add_concrete(&mut choices, &self.struct_types); } NoExtern => { - choices.push(ht(Extern)); + add_abstract(&mut choices, &[Extern]); } NoFunc => { - choices.extend(self.func_types.iter().copied().map(HT::Concrete)); - choices.push(ht(Func)); + add_abstract(&mut choices, &[Func]); + add_concrete(&mut choices, &self.func_types); } NoExn => { - choices.push(ht(Exn)); + add_abstract(&mut choices, &[Exn]); } Struct | Array | I31 => { - choices.extend([ht(Any), ht(Eq)]); + add_abstract(&mut choices, &[Any, Eq]); } Eq => { - choices.push(ht(Any)); + add_abstract(&mut choices, &[Any]); } NoCont => { - choices.push(ht(Cont)); + add_abstract(&mut choices, &[Cont]); } Exn | Any | Func | Extern | Cont => {} } } HT::Concrete(mut idx) => { if let Some(sub_ty) = &self.types.get(usize::try_from(idx).unwrap()) { + use AbstractHeapType::*; let ht = |ty| HT::Abstract { shared: sub_ty.composite_type.shared, ty, }; match &sub_ty.composite_type.inner { CT::Array(_) => { - choices.extend([ - ht(AbstractHeapType::Any), - ht(AbstractHeapType::Eq), - ht(AbstractHeapType::Array), - ]); + choices.extend([ht(Any), ht(Eq), ht(Array)]); } CT::Func(_) => { - choices.push(ht(AbstractHeapType::Func)); + choices.push(ht(Func)); } CT::Struct(_) => { - choices.extend([ - ht(AbstractHeapType::Any), - ht(AbstractHeapType::Eq), - ht(AbstractHeapType::Struct), - ]); + choices.extend([ht(Any), ht(Eq), ht(Struct)]); } } } else { @@ -1001,26 +1026,29 @@ impl Module { fn arbitrary_composite_type(&mut self, u: &mut Unstructured) -> Result { use CompositeInnerType as CT; - let shared = false; // TODO: handle shared + let shared = self.arbitrary_shared(u)?; + if !self.config.gc_enabled { return Ok(CompositeType { shared, - inner: CT::Func(self.arbitrary_func_type(u)?), + inner: CT::Func(self.propagate_shared(shared, |m| m.arbitrary_func_type(u))?), }); } match u.int_in_range(0..=2)? { 0 => Ok(CompositeType { shared, - inner: CT::Array(ArrayType(self.arbitrary_field_type(u)?)), + inner: CT::Array(ArrayType( + self.propagate_shared(shared, |m| m.arbitrary_field_type(u))?, + )), }), 1 => Ok(CompositeType { shared, - inner: CT::Func(self.arbitrary_func_type(u)?), + inner: CT::Func(self.propagate_shared(shared, |m| m.arbitrary_func_type(u))?), }), 2 => Ok(CompositeType { shared, - inner: CT::Struct(self.arbitrary_struct_type(u)?), + inner: CT::Struct(self.propagate_shared(shared, |m| m.arbitrary_struct_type(u))?), }), _ => unreachable!(), } @@ -1073,7 +1101,18 @@ impl Module { if self.config.gc_enabled && concrete_type_limit > 0 && u.arbitrary()? { let idx = u.int_in_range(0..=concrete_type_limit - 1)?; - return Ok(HeapType::Concrete(idx)); + // If the caller is demanding a shared heap type but the concrete + // type we found is not in fact shared, we skip down below to use an + // abstract heap type instead. If the caller is not demanding a + // shared type, though, we can use either a shared or unshared + // concrete type. + if let Some(ty) = self.types.get(idx as usize) { + // TODO: in the future, once we can easily query a list of + // existing shared types, remove this extra check. + if !(self.must_share && !ty.composite_type.shared) { + return Ok(HeapType::Concrete(idx)); + } + } } use AbstractHeapType::*; @@ -1090,7 +1129,7 @@ impl Module { } Ok(HeapType::Abstract { - shared: false, // TODO: turn on shared attribute with shared-everything-threads. + shared: self.arbitrary_shared(u)?, ty: *u.choose(&choices)?, }) } @@ -1305,7 +1344,7 @@ impl Module { // Returns the index to the translated type in the to-be type section, and the reference to // the type itself. - let mut make_func_type = |parsed_sig_idx: u32| { + let mut make_func_type = |module: &Self, parsed_sig_idx: u32| { let serialized_sig_idx = match available_types.get_mut(parsed_sig_idx as usize) { None => panic!("signature index refers to a type out of bounds"), Some((_, Some(idx))) => *idx as usize, @@ -1328,10 +1367,11 @@ impl Module { .collect(), }); index_store.replace(new_index as u32); + let shared = module.arbitrary_shared(u).ok()?; new_types.push(SubType { is_final: true, supertype: None, - composite_type: CompositeType::new_func(Rc::clone(&func_type), false), // TODO: handle shared + composite_type: CompositeType::new_func(Rc::clone(&func_type), shared), }); new_index } @@ -1351,7 +1391,7 @@ impl Module { wasmparser::TypeRef::Func(sig_idx) => { if self.funcs.len() >= self.config.max_funcs { continue; - } else if let Some((sig_idx, func_type)) = make_func_type(*sig_idx) { + } else if let Some((sig_idx, func_type)) = make_func_type(&self, *sig_idx) { let entity = EntityType::Func(sig_idx as u32, Rc::clone(&func_type)); if type_size_budget < entity.size() { continue; @@ -1367,7 +1407,8 @@ impl Module { let can_add_tag = self.tags.len() < self.config.max_tags; if !self.config.exceptions_enabled || !can_add_tag { continue; - } else if let Some((sig_idx, func_type)) = make_func_type(*func_type_idx) { + } else if let Some((sig_idx, func_type)) = make_func_type(&self, *func_type_idx) + { let tag_type = TagType { func_type_idx: sig_idx, func_type, @@ -1530,10 +1571,18 @@ impl Module { } fn arbitrary_global_type(&self, u: &mut Unstructured) -> Result { + let val_type = self.arbitrary_valtype(u)?; + // Propagate the inner type's sharedness to the global type. + let shared = match val_type { + ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => { + self.arbitrary_shared(u)? + } + ValType::Ref(r) => self.is_shared_ref_type(r), + }; Ok(GlobalType { - val_type: self.arbitrary_valtype(u)?, + val_type, mutable: u.arbitrary()?, - shared: false, + shared, }) } @@ -1564,12 +1613,26 @@ impl Module { return Ok(()); } + // For now, only define non-shared functions. Until we can update + // instruction generation to understand the additional sharedness + // validation, we don't want to generate instructions that touch + // unshared objects from a shared context (TODO: handle shared). + let unshared_func_types: Vec<_> = self + .func_types + .iter() + .copied() + .filter(|&i| !self.is_shared_type(i)) + .collect(); + if unshared_func_types.is_empty() { + return Ok(()); + } + arbitrary_loop(u, self.config.min_funcs, self.config.max_funcs, |u| { if !self.can_add_local_or_import_func() { return Ok(false); } - let max = self.func_types.len() - 1; - let ty = self.func_types[u.int_in_range(0..=max)?]; + let max = unshared_func_types.len() - 1; + let ty = unshared_func_types[u.int_in_range(0..=max)?]; self.funcs.push((ty, self.func_type(ty).clone())); self.num_defined_funcs += 1; Ok(true) @@ -1650,7 +1713,6 @@ impl Module { fn arbitrary_const_expr(&mut self, ty: ValType, u: &mut Unstructured) -> Result { let mut choices = mem::take(&mut self.const_expr_choices); choices.clear(); - let num_funcs = self.funcs.len() as u32; // MVP wasm can `global.get` any immutable imported global in a // constant expression, and the GC proposal enables this for all @@ -1690,12 +1752,25 @@ impl Module { match ty.heap_type { HeapType::Abstract { ty: AbstractHeapType::Func, - .. - } if num_funcs > 0 => { - choices.push(Box::new(move |u, _| { - let func = u.int_in_range(0..=num_funcs - 1)?; - Ok(ConstExpr::ref_func(func)) - })); + shared, + } => { + let num_funcs = self + .funcs + .iter() + .filter(|(t, _)| shared == self.is_shared_type(*t)) + .count(); + if num_funcs > 0 { + let pick = u.int_in_range(0..=num_funcs - 1)?; + let (i, _) = self + .funcs + .iter() + .map(|(t, _)| *t) + .enumerate() + .filter(|(_, t)| shared == self.is_shared_type(*t)) + .nth(pick) + .unwrap(); + choices.push(Box::new(move |_, _| Ok(ConstExpr::ref_func(i as u32)))); + } } HeapType::Concrete(ty) => { @@ -1883,8 +1958,8 @@ impl Module { supertype: None, composite_type: CompositeType::new_func( Rc::clone(&new_type), - false, - ), // TODO: handle shared + subtype.composite_type.shared, + ), }); let func_index = self.funcs.len() as u32; self.funcs.push((type_index, new_type)); @@ -2202,8 +2277,9 @@ impl Module { self.code.reserve(self.num_defined_funcs); let mut allocs = CodeBuilderAllocations::new(self, self.config.exports.is_some()); - for (_, ty) in self.funcs[self.funcs.len() - self.num_defined_funcs..].iter() { - let body = self.arbitrary_func_body(u, ty, &mut allocs)?; + for (idx, ty) in self.funcs[self.funcs.len() - self.num_defined_funcs..].iter() { + let shared = self.is_shared_type(*idx); + let body = self.arbitrary_func_body(u, ty, &mut allocs, shared)?; self.code.push(body); } allocs.finish(u, self)?; @@ -2215,9 +2291,10 @@ impl Module { u: &mut Unstructured, ty: &FuncType, allocs: &mut CodeBuilderAllocations, + shared: bool, ) -> Result { let mut locals = self.arbitrary_locals(u)?; - let builder = allocs.builder(ty, &mut locals); + let builder = allocs.builder(ty, &mut locals, shared); let instructions = if self.config.allow_invalid_funcs && u.arbitrary().unwrap_or(false) { Instructions::Arbitrary(arbitrary_vec_u8(u)?) } else { @@ -2549,6 +2626,34 @@ impl Module { } } } + + fn propagate_shared(&mut self, must_share: bool, mut f: impl FnMut(&mut Self) -> T) -> T { + let tmp = mem::replace(&mut self.must_share, must_share); + let result = f(self); + self.must_share = tmp; + result + } + + fn arbitrary_shared(&self, u: &mut Unstructured) -> Result { + if self.must_share { + Ok(true) + } else { + Ok(self.config.shared_everything_threads_enabled && u.ratio(1, 4)?) + } + } + + fn is_shared_ref_type(&self, ty: RefType) -> bool { + match ty.heap_type { + HeapType::Abstract { shared, .. } => shared, + HeapType::Concrete(i) => self.types[i as usize].composite_type.shared, + } + } + + fn is_shared_type(&self, index: u32) -> bool { + let index = usize::try_from(index).unwrap(); + let ty = self.types.get(index).unwrap(); + ty.composite_type.shared + } } pub(crate) fn arbitrary_limits64( @@ -2604,14 +2709,20 @@ pub(crate) fn configured_valtypes(config: &Config) -> Vec { true, ] { use AbstractHeapType::*; - for ty in [ + let abs_ref_types = [ Any, Eq, I31, Array, Struct, None, Func, NoFunc, Extern, NoExtern, - ] { - valtypes.push(ValType::Ref(RefType { - nullable, - // TODO: handle shared - heap_type: HeapType::Abstract { shared: false, ty }, - })); + ]; + valtypes.extend( + abs_ref_types + .iter() + .map(|&ty| ValType::Ref(RefType::new_abstract(ty, nullable, false))), + ); + if config.shared_everything_threads_enabled { + valtypes.extend( + abs_ref_types + .iter() + .map(|&ty| ValType::Ref(RefType::new_abstract(ty, nullable, true))), + ); } } } else if config.reference_types_enabled { @@ -2646,12 +2757,19 @@ pub(crate) fn arbitrary_table_type( Some(module) => module.arbitrary_ref_type(u)?, None => RefType::FUNCREF, }; + + // Propagate the element type's sharedness to the table type. + let shared = match module { + Some(module) => module.is_shared_ref_type(element_type), + None => false, + }; + Ok(TableType { element_type, minimum, maximum, table64, - shared: false, // TODO: handle shared + shared, }) } diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 310800bf8a..c8c2671474 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -668,6 +668,8 @@ pub(crate) struct CodeBuilderAllocations { } pub(crate) struct CodeBuilder<'a> { + #[allow(dead_code)] + shared: bool, func_ty: &'a FuncType, locals: &'a mut Vec, allocs: &'a mut CodeBuilderAllocations, @@ -903,6 +905,7 @@ impl CodeBuilderAllocations { &'a mut self, func_ty: &'a FuncType, locals: &'a mut Vec, + shared: bool, ) -> CodeBuilder<'a> { self.controls.clear(); self.controls.push(Control { @@ -916,6 +919,7 @@ impl CodeBuilderAllocations { self.options.clear(); CodeBuilder { + shared, func_ty, locals, allocs: self, @@ -2323,8 +2327,8 @@ fn br_on_null( let ty = *u.choose(&[ Func, Extern, Any, None, NoExtern, NoFunc, Eq, Struct, Array, I31, ])?; - // TODO: handle shared - HeapType::Abstract { shared: false, ty } + let shared = module.arbitrary_shared(u)?; + HeapType::Abstract { shared, ty } } } }; @@ -5397,25 +5401,26 @@ fn ref_null( } if module.config.gc_enabled { use AbstractHeapType::*; - let r = |heap_type| RefType { - nullable: true, - heap_type, - }; - let a = |abstract_heap_type| HeapType::Abstract { - shared: false, // TODO: handle shared - ty: abstract_heap_type, - }; - choices.push(r(a(Any))); - choices.push(r(a(Eq))); - choices.push(r(a(Array))); - choices.push(r(a(Struct))); - choices.push(r(a(I31))); - choices.push(r(a(None))); - choices.push(r(a(NoFunc))); - choices.push(r(a(NoExtern))); + let abs_ref_types = [Any, Eq, Array, Struct, I31, None, NoFunc, NoExtern]; + choices.extend( + abs_ref_types + .iter() + .map(|&ty| RefType::new_abstract(ty, true, false)), + ); + if module.config().shared_everything_threads_enabled { + choices.extend( + abs_ref_types + .iter() + .map(|&ty| RefType::new_abstract(ty, true, true)), + ); + } + for i in 0..module.types.len() { let i = u32::try_from(i).unwrap(); - choices.push(r(HeapType::Concrete(i))); + choices.push(RefType { + nullable: true, + heap_type: HeapType::Concrete(i), + }); } } let ty = *u.choose(&choices)?; diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index f0d70ed9ed..66942dec6b 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -27,6 +27,7 @@ pub fn generate_valid_module( config.canonicalize_nans = u.arbitrary()?; config.custom_page_sizes_enabled = u.arbitrary()?; config.wide_arithmetic_enabled = u.arbitrary()?; + config.shared_everything_threads_enabled = u.arbitrary()?; configure(&mut config, u)?; diff --git a/fuzz/src/mutate.rs b/fuzz/src/mutate.rs index d1492f1d2e..f0923d2e7d 100644 --- a/fuzz/src/mutate.rs +++ b/fuzz/src/mutate.rs @@ -11,12 +11,16 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { let mut seed = 0; let mut preserve_semantics = false; - let (wasm, _config) = crate::generate_valid_module(u, |_config, u| { + let (wasm, _config) = crate::generate_valid_module(u, |config, u| { // NB: wasm-mutate is a general-purpose tool so unsupported proposals by // wasm-mutate are not disabled here. Those must be rejected with a // first-class error in wasm-mutate instead of panicking. seed = u.arbitrary()?; preserve_semantics = u.arbitrary()?; + + // NB: the mutator will change shared to unshared in ways that create + // invalid modules so we disable the proposal (TODO: handle shared). + config.shared_everything_threads_enabled = false; Ok(()) })?; log::debug!("seed = {}", seed);