Skip to content

Commit b8f0db3

Browse files
authored
threads: improve tests (#1719)
* threads: rename tests This change simply renames the shared-everything-threads tests to use more conventional names within the repository. * threads: add more Binaryen tests @tlively has been working on Binaryen support for shared-everything-threads and has upstreamed several [tests] that are useful here. This change incorporates some "bottom type" checks for arrays and structs as a `shared-ref_eq.wast` test checking `ref.eq` behavior for shared types (renamed to `ref-equivalence.wast` here). To make these added tests pass, validation for `ref.eq` now allows it to check both shared and ushared `eqref` types the the `ref.i31_shared` returns the correct type. [tests]: https://github.com/WebAssembly/binaryen/blob/main/test/spec [shared-ref_eq.wast]: https://github.com/WebAssembly/binaryen/blob/main/test/spec/shared-ref_eq.wast * threads: add polymorphism over `shared` Certain instructions accept both shared and unshared references: `ref.eq`, `i31.get_s`, `i31.get_u`, `array.len`, `any.convert_extern`, and `extern.convert_any`. Validation needs to handle these special cases to pass Binaryen's `polymorphism.wast` test. This change refactors the `pop_operand` family of functions with several helpers to add `pop_maybe_shared_ref`; `pop_maybe_shared_ref` lets the validator pass an unshared `RefType` expectation but adapts it to being shared if that is what is on the stack. * Undo previous refactoring; reuse `OperatorValidatorTemp::pop_ref` This change undoes previous work to rationalize the duplication in `_pop_operand`, `pop_ref`, and now `pop_maybe_shared_ref`. As suggested by @alexcrichton, this attempt keeps the status quo by reusing `pop_ref` and doing a bit more work after the fact (re-checking the subtype relationship). * Handle `ref.eq` types from unreachable code This validator's existing logic assumed that the bottom and heap bottom types matched any reference type (see `_pop_operand`). This change surfaces that logic to `visit_ref_eq` as well: if `pop_maybe_shared_ref` returns `None` (a bottom or heap bottom) for either value to `ref.eq`, we skip checking the shared-ness match, assuming like we do in `_pop_operand` that a bottom or heap bottom matches any reference type (we've already checked that the types are subtypes of `eqref`). * Add TODO for unshared propagation in unreachable code * Return shared `bool` from `pop_maybe_shared_ref` * Rename `is_shared_ref_type` to `is_shared`
1 parent 88a090d commit b8f0db3

110 files changed

Lines changed: 2884 additions & 303 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/wasmparser/src/resources.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ pub trait WasmModuleResources {
6767
/// Is `a` a subtype of `b`?
6868
fn is_subtype(&self, a: ValType, b: ValType) -> bool;
6969

70+
/// Is the given reference type `shared`?
71+
///
72+
/// While abstract heap types do carry along a `shared` flag, concrete heap
73+
/// types do not. This function resolves those concrete heap types to
74+
/// determine `shared`-ness.
75+
fn is_shared(&self, ty: RefType) -> bool;
76+
7077
/// Check and canonicalize a value type.
7178
///
7279
/// This will validate that `t` is valid under the `features` provided and
@@ -161,6 +168,9 @@ where
161168
fn is_subtype(&self, a: ValType, b: ValType) -> bool {
162169
T::is_subtype(self, a, b)
163170
}
171+
fn is_shared(&self, ty: RefType) -> bool {
172+
T::is_shared(self, ty)
173+
}
164174
fn element_count(&self) -> u32 {
165175
T::element_count(self)
166176
}
@@ -220,6 +230,10 @@ where
220230
T::is_subtype(self, a, b)
221231
}
222232

233+
fn is_shared(&self, ty: RefType) -> bool {
234+
T::is_shared(self, ty)
235+
}
236+
223237
fn element_count(&self) -> u32 {
224238
T::element_count(self)
225239
}

crates/wasmparser/src/validator/core.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,10 @@ impl WasmModuleResources for OperatorValidatorResources<'_> {
13221322
self.types.valtype_is_subtype(a, b)
13231323
}
13241324

1325+
fn is_shared(&self, ty: RefType) -> bool {
1326+
self.types.reftype_is_shared(ty)
1327+
}
1328+
13251329
fn element_count(&self) -> u32 {
13261330
self.module.element_types.len() as u32
13271331
}
@@ -1394,6 +1398,10 @@ impl WasmModuleResources for ValidatorResources {
13941398
self.0.snapshot.as_ref().unwrap().valtype_is_subtype(a, b)
13951399
}
13961400

1401+
fn is_shared(&self, ty: RefType) -> bool {
1402+
self.0.snapshot.as_ref().unwrap().reftype_is_shared(ty)
1403+
}
1404+
13971405
fn element_count(&self) -> u32 {
13981406
self.0.element_types.len() as u32
13991407
}

crates/wasmparser/src/validator/func.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ impl<T: WasmModuleResources> FuncValidator<T> {
237237
mod tests {
238238
use super::*;
239239
use crate::types::CoreTypeId;
240-
use crate::HeapType;
240+
use crate::{HeapType, RefType};
241241

242242
struct EmptyResources(crate::SubType);
243243

@@ -288,6 +288,9 @@ mod tests {
288288
fn is_subtype(&self, _t1: ValType, _t2: ValType) -> bool {
289289
todo!()
290290
}
291+
fn is_shared(&self, _ty: RefType) -> bool {
292+
todo!()
293+
}
291294
fn element_count(&self) -> u32 {
292295
todo!()
293296
}

crates/wasmparser/src/validator/operators.rs

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -199,15 +199,6 @@ impl From<RefType> for MaybeType {
199199
}
200200
}
201201

202-
impl MaybeType {
203-
fn as_type(&self) -> Option<ValType> {
204-
match *self {
205-
Self::Type(ty) => Some(ty),
206-
Self::Bot | Self::HeapBot => None,
207-
}
208-
}
209-
}
210-
211202
impl OperatorValidator {
212203
fn new(features: &WasmFeatures, allocs: OperatorValidatorAllocations) -> Self {
213204
let OperatorValidatorAllocations {
@@ -613,6 +604,7 @@ where
613604
Ok(actual)
614605
}
615606

607+
/// Pop a reference type from the operand stack.
616608
fn pop_ref(&mut self) -> Result<Option<RefType>> {
617609
match self.pop_operand(None)? {
618610
MaybeType::Bot | MaybeType::HeapBot => Ok(None),
@@ -625,6 +617,40 @@ where
625617
}
626618
}
627619

620+
/// Pop a reference type from the operand stack, checking if it is a subtype
621+
/// of `expected` or the shared version of `expected`. This function returns
622+
/// the popped reference type and its `shared`-ness, saving extra lookups
623+
/// for concrete types. This function will panic if `expected` is shared or
624+
/// a concrete type.
625+
fn pop_maybe_shared_ref(&mut self, expected: RefType) -> Result<Option<(RefType, bool)>> {
626+
debug_assert!(!expected.is_concrete_type_ref());
627+
assert!(!self.resources.is_shared(expected));
628+
let actual = match self.pop_ref()? {
629+
Some(rt) => rt,
630+
None => return Ok(None),
631+
};
632+
// Change our expectation based on whether we're dealing with an actual
633+
// shared or unshared type.
634+
let is_actual_shared = self.resources.is_shared(actual);
635+
let expected = if is_actual_shared {
636+
expected
637+
.shared()
638+
.expect("this only expects abstract heap types")
639+
} else {
640+
expected
641+
};
642+
// Check (again) that the actual type is a subtype of the expected type.
643+
// Note that `_pop_operand` already does this kind of thing but we leave
644+
// that for a future refactoring (TODO).
645+
if !self.resources.is_subtype(actual.into(), expected.into()) {
646+
bail!(
647+
self.offset,
648+
"type mismatch: expected subtype of {expected}, found {actual}",
649+
)
650+
}
651+
Ok(Some((actual, is_actual_shared)))
652+
}
653+
628654
/// Fetches the type for the local at `idx`, returning an error if it's out
629655
/// of bounds.
630656
fn local(&self, idx: u32) -> Result<ValType> {
@@ -2723,8 +2749,22 @@ where
27232749
Ok(())
27242750
}
27252751
fn visit_ref_eq(&mut self) -> Self::Output {
2726-
self.pop_operand(Some(RefType::EQ.nullable().into()))?;
2727-
self.pop_operand(Some(RefType::EQ.nullable().into()))?;
2752+
let a = self.pop_maybe_shared_ref(RefType::EQ.nullable())?;
2753+
let b = self.pop_maybe_shared_ref(RefType::EQ.nullable())?;
2754+
match (a, b) {
2755+
(Some((_, is_a_shared)), Some((_, is_b_shared))) => {
2756+
if is_a_shared != is_b_shared {
2757+
bail!(
2758+
self.offset,
2759+
"type mismatch: expected `ref.eq` types to match `shared`-ness"
2760+
);
2761+
}
2762+
}
2763+
_ => {
2764+
// One or both of the types are from unreachable code; assume
2765+
// the shared-ness matches.
2766+
}
2767+
}
27282768
self.push_operand(ValType::I32)
27292769
}
27302770
fn visit_v128_load(&mut self, memarg: MemArg) -> Self::Output {
@@ -4224,7 +4264,7 @@ where
42244264
Ok(())
42254265
}
42264266
fn visit_array_len(&mut self) -> Self::Output {
4227-
self.pop_operand(Some(RefType::ARRAY.nullable().into()))?;
4267+
self.pop_maybe_shared_ref(RefType::ARRAY.nullable())?;
42284268
self.push_operand(ValType::I32)
42294269
}
42304270
fn visit_array_fill(&mut self, array_type_index: u32) -> Self::Output {
@@ -4406,24 +4446,32 @@ where
44064446
Ok(())
44074447
}
44084448
fn visit_any_convert_extern(&mut self) -> Self::Output {
4409-
let extern_ref = self.pop_operand(Some(RefType::EXTERNREF.into()))?;
4410-
let is_nullable = extern_ref
4411-
.as_type()
4412-
.map_or(false, |ty| ty.as_reference_type().unwrap().is_nullable());
4449+
let extern_ref = self.pop_maybe_shared_ref(RefType::EXTERNREF)?;
4450+
let (is_nullable, shared) = if let Some((extern_ref, shared)) = extern_ref {
4451+
(extern_ref.is_nullable(), shared)
4452+
} else {
4453+
// TODO: propagating unshared may be incorrect here
4454+
// (https://github.com/WebAssembly/shared-everything-threads/issues/80)
4455+
(false, false)
4456+
};
44134457
let heap_type = HeapType::Abstract {
4414-
shared: false, // TODO: handle shared--see https://github.com/WebAssembly/shared-everything-threads/issues/65.
4458+
shared,
44154459
ty: AbstractHeapType::Any,
44164460
};
44174461
let any_ref = RefType::new(is_nullable, heap_type).unwrap();
44184462
self.push_operand(any_ref)
44194463
}
44204464
fn visit_extern_convert_any(&mut self) -> Self::Output {
4421-
let any_ref = self.pop_operand(Some(RefType::ANY.nullable().into()))?;
4422-
let is_nullable = any_ref
4423-
.as_type()
4424-
.map_or(false, |ty| ty.as_reference_type().unwrap().is_nullable());
4465+
let any_ref = self.pop_maybe_shared_ref(RefType::ANY.nullable())?;
4466+
let (is_nullable, shared) = if let Some((any_ref, shared)) = any_ref {
4467+
(any_ref.is_nullable(), shared)
4468+
} else {
4469+
// TODO: propagating unshared may be incorrect here
4470+
// (https://github.com/WebAssembly/shared-everything-threads/issues/80)
4471+
(false, false)
4472+
};
44254473
let heap_type = HeapType::Abstract {
4426-
shared: false, // TODO: handle shared--see https://github.com/WebAssembly/shared-everything-threads/issues/65.
4474+
shared,
44274475
ty: AbstractHeapType::Extern,
44284476
};
44294477
let extern_ref = RefType::new(is_nullable, heap_type).unwrap();
@@ -4539,11 +4587,11 @@ where
45394587
))
45404588
}
45414589
fn visit_i31_get_s(&mut self) -> Self::Output {
4542-
self.pop_operand(Some(ValType::Ref(RefType::I31REF)))?;
4590+
self.pop_maybe_shared_ref(RefType::I31REF)?;
45434591
self.push_operand(ValType::I32)
45444592
}
45454593
fn visit_i31_get_u(&mut self) -> Self::Output {
4546-
self.pop_operand(Some(ValType::Ref(RefType::I31REF)))?;
4594+
self.pop_maybe_shared_ref(RefType::I31REF)?;
45474595
self.push_operand(ValType::I32)
45484596
}
45494597
fn visit_try(&mut self, mut ty: BlockType) -> Self::Output {

tests/local/shared-everything-threads/array.wast renamed to tests/local/shared-everything-threads/arrays.wast

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,31 @@
114114
(array.init_data $i8 0 (local.get 0) (i32.const 0) (i32.const 0) (i32.const 0)))
115115
)
116116

117-
;; Check `array.atomic.rmw.*` instructions.
117+
;; Bottom types can be used as shared arrays.
118+
(module
119+
(type $i8 (shared (array (mut i8))))
120+
(type $i32 (shared (array (mut i32))))
121+
(type $funcs (shared (array (mut (ref null (shared func))))))
122+
123+
(data)
124+
;; See https://github.com/bytecodealliance/wasm-tools/issues/1717.
125+
(elem (ref null (shared func)))
126+
127+
(func (array.get_s $i8 (ref.null (shared none)) (i32.const 0)) (drop))
128+
(func (array.get_u $i8 (ref.null (shared none)) (i32.const 0)) (drop))
129+
(func (array.get $i32 (ref.null (shared none)) (i32.const 0)) (drop))
130+
(func (array.set $i8 (ref.null (shared none)) (i32.const 0) (i32.const 0)))
131+
(func (param (ref null $i8))
132+
(array.copy $i8 $i8 (ref.null (shared none)) (i32.const 0) (local.get 0) (i32.const 0) (i32.const 0)))
133+
(func (param (ref null $i8))
134+
(array.copy $i8 $i8 (local.get 0) (i32.const 0) (ref.null (shared none)) (i32.const 0) (i32.const 0)))
135+
(func (array.copy $i8 $i8 (ref.null (shared none)) (i32.const 0) (ref.null (shared none)) (i32.const 0) (i32.const 0)))
136+
(func (array.fill $i8 (ref.null (shared none)) (i32.const 0) (i32.const 0) (i32.const 0)))
137+
(func (array.init_data $i8 0 (ref.null (shared none)) (i32.const 0) (i32.const 0) (i32.const 0)))
138+
(func (array.init_elem $funcs 0 (ref.null (shared none)) (i32.const 0) (i32.const 0) (i32.const 0)))
139+
)
140+
141+
;; Exhaustively check `array.atomic.rmw.*` instructions.
118142
(module (; get, i32, seq_cst ;)
119143
(type $a (shared (array (mut i32))))
120144
(func (export "array-atomic-get-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (result i32)
File renamed without changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
;; Some instructions are shared-polymorphic and work with shared or unshared
2+
;; references.
3+
(module
4+
(func (drop (ref.eq (ref.null (shared none)) (ref.null (shared none)))))
5+
6+
(func (param (ref null (shared i31))) (drop (i31.get_s (local.get 0))))
7+
(func (param (ref null (shared i31))) (drop (i31.get_u (local.get 0))))
8+
9+
(func (param (ref null (shared array))) (drop (array.len (local.get 0))))
10+
11+
(func (param (ref null (shared extern))) (result (ref null (shared any)))
12+
(any.convert_extern (local.get 0))
13+
)
14+
(func (param (ref (shared extern))) (result (ref (shared any)))
15+
(any.convert_extern (local.get 0))
16+
)
17+
(func (param (ref null (shared any))) (result (ref null (shared extern)))
18+
(extern.convert_any (local.get 0))
19+
)
20+
(func (param (ref (shared any))) (result (ref (shared extern)))
21+
(extern.convert_any (local.get 0))
22+
)
23+
)

0 commit comments

Comments
 (0)