Skip to content

Commit 405e529

Browse files
authored
Implement support for thread in *.wast tests (#7289)
* Implement support for `thread` in `*.wast` tests This commit implements support for `thread` and `wait` in `*.wast` files and imports the upstream spec test suite from the `threads` proposal. This additionally and hopefully makes it a bit easier to write threaded tests in the future if necessary too. * Fix compile of fuzzing
1 parent cc3bf3c commit 405e529

File tree

10 files changed

+573
-37
lines changed

10 files changed

+573
-37
lines changed

crates/fuzzing/src/oracles.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ use self::engine::{DiffEngine, DiffInstance};
2323
use crate::generators::{self, DiffValue, DiffValueType};
2424
use arbitrary::Arbitrary;
2525
pub use stacks::check_stacks;
26-
use std::cell::Cell;
27-
use std::rc::Rc;
28-
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
26+
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
2927
use std::sync::{Arc, Condvar, Mutex};
3028
use std::time::{Duration, Instant};
3129
use wasmtime::*;
@@ -62,43 +60,44 @@ pub fn log_wasm(wasm: &[u8]) {
6260
/// The `T` in `Store<T>` for fuzzing stores, used to limit resource
6361
/// consumption during fuzzing.
6462
#[derive(Clone)]
65-
pub struct StoreLimits(Rc<LimitsState>);
63+
pub struct StoreLimits(Arc<LimitsState>);
6664

6765
struct LimitsState {
6866
/// Remaining memory, in bytes, left to allocate
69-
remaining_memory: Cell<usize>,
67+
remaining_memory: AtomicUsize,
7068
/// Whether or not an allocation request has been denied
71-
oom: Cell<bool>,
69+
oom: AtomicBool,
7270
}
7371

7472
impl StoreLimits {
7573
/// Creates the default set of limits for all fuzzing stores.
7674
pub fn new() -> StoreLimits {
77-
StoreLimits(Rc::new(LimitsState {
75+
StoreLimits(Arc::new(LimitsState {
7876
// Limits tables/memories within a store to at most 1gb for now to
7977
// exercise some larger address but not overflow various limits.
80-
remaining_memory: Cell::new(1 << 30),
81-
oom: Cell::new(false),
78+
remaining_memory: AtomicUsize::new(1 << 30),
79+
oom: AtomicBool::new(false),
8280
}))
8381
}
8482

8583
fn alloc(&mut self, amt: usize) -> bool {
8684
log::trace!("alloc {amt:#x} bytes");
87-
match self.0.remaining_memory.get().checked_sub(amt) {
88-
Some(mem) => {
89-
self.0.remaining_memory.set(mem);
90-
true
91-
}
92-
None => {
93-
self.0.oom.set(true);
85+
match self
86+
.0
87+
.remaining_memory
88+
.fetch_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(amt))
89+
{
90+
Ok(_) => true,
91+
Err(_) => {
92+
self.0.oom.store(true, SeqCst);
9493
log::debug!("OOM hit");
9594
false
9695
}
9796
}
9897
}
9998

10099
fn is_oom(&self) -> bool {
101-
self.0.oom.get()
100+
self.0.oom.load(SeqCst)
102101
}
103102
}
104103

crates/wast/src/wast.rs

Lines changed: 83 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ use crate::component;
33
use crate::core;
44
use crate::spectest::*;
55
use anyhow::{anyhow, bail, Context as _, Error, Result};
6+
use std::collections::HashMap;
67
use std::path::Path;
78
use std::str;
9+
use std::thread;
810
use wasmtime::*;
911
use wast::lexer::Lexer;
1012
use wast::parser::{self, ParseBuffer};
@@ -62,7 +64,10 @@ enum Export {
6264
Component(component::Func),
6365
}
6466

65-
impl<T> WastContext<T> {
67+
impl<T> WastContext<T>
68+
where
69+
T: Clone + Send + 'static,
70+
{
6671
/// Construct a new instance of `WastContext`.
6772
pub fn new(store: Store<T>) -> Self {
6873
// Spec tests will redefine the same module/name sometimes, so we need
@@ -361,26 +366,54 @@ impl<T> WastContext<T> {
361366
let buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
362367
let ast = parser::parse::<Wast>(&buf).map_err(adjust_wast)?;
363368

364-
for directive in ast.directives {
365-
let sp = directive.span();
366-
if log::log_enabled!(log::Level::Debug) {
367-
let (line, col) = sp.linecol_in(wast);
368-
log::debug!("running directive on {}:{}:{}", filename, line + 1, col);
369-
}
370-
self.run_directive(directive)
371-
.map_err(|e| match e.downcast() {
372-
Ok(err) => adjust_wast(err).into(),
373-
Err(e) => e,
374-
})
375-
.with_context(|| {
369+
self.run_directives(ast.directives, filename, wast)
370+
}
371+
372+
fn run_directives(
373+
&mut self,
374+
directives: Vec<WastDirective<'_>>,
375+
filename: &str,
376+
wast: &str,
377+
) -> Result<()> {
378+
let adjust_wast = |mut err: wast::Error| {
379+
err.set_path(filename.as_ref());
380+
err.set_text(wast);
381+
err
382+
};
383+
384+
thread::scope(|scope| {
385+
let mut threads = HashMap::new();
386+
for directive in directives {
387+
let sp = directive.span();
388+
if log::log_enabled!(log::Level::Debug) {
376389
let (line, col) = sp.linecol_in(wast);
377-
format!("failed directive on {}:{}:{}", filename, line + 1, col)
378-
})?;
379-
}
380-
Ok(())
390+
log::debug!("running directive on {}:{}:{}", filename, line + 1, col);
391+
}
392+
self.run_directive(directive, filename, wast, &scope, &mut threads)
393+
.map_err(|e| match e.downcast() {
394+
Ok(err) => adjust_wast(err).into(),
395+
Err(e) => e,
396+
})
397+
.with_context(|| {
398+
let (line, col) = sp.linecol_in(wast);
399+
format!("failed directive on {}:{}:{}", filename, line + 1, col)
400+
})?;
401+
}
402+
Ok(())
403+
})
381404
}
382405

383-
fn run_directive(&mut self, directive: WastDirective) -> Result<()> {
406+
fn run_directive<'a>(
407+
&mut self,
408+
directive: WastDirective<'a>,
409+
filename: &'a str,
410+
wast: &'a str,
411+
scope: &'a thread::Scope<'a, '_>,
412+
threads: &mut HashMap<&'a str, thread::ScopedJoinHandle<'a, Result<()>>>,
413+
) -> Result<()>
414+
where
415+
T: 'a,
416+
{
384417
use wast::WastDirective::*;
385418

386419
match directive {
@@ -466,9 +499,39 @@ impl<T> WastContext<T> {
466499
}
467500
AssertException { .. } => bail!("unimplemented assert_exception"),
468501

469-
Thread(_) => bail!("unimplemented `thread`"),
502+
Thread(thread) => {
503+
let mut core_linker = Linker::new(self.store.engine());
504+
if let Some(id) = thread.shared_module {
505+
let items = self
506+
.core_linker
507+
.iter(&mut self.store)
508+
.filter(|(module, _, _)| *module == id.name())
509+
.collect::<Vec<_>>();
510+
for (module, name, item) in items {
511+
core_linker.define(&mut self.store, module, name, item)?;
512+
}
513+
}
514+
let mut child_cx = WastContext {
515+
current: None,
516+
core_linker,
517+
#[cfg(feature = "component-model")]
518+
component_linker: component::Linker::new(self.store.engine()),
519+
store: Store::new(self.store.engine(), self.store.data().clone()),
520+
};
521+
let name = thread.name.name();
522+
let child =
523+
scope.spawn(move || child_cx.run_directives(thread.directives, filename, wast));
524+
threads.insert(name, child);
525+
}
470526

471-
Wait { .. } => bail!("unimplemented `wait`"),
527+
Wait { thread, .. } => {
528+
let name = thread.name();
529+
threads
530+
.remove(name)
531+
.ok_or_else(|| anyhow!("no thread named `{name}`"))?
532+
.join()
533+
.unwrap()?;
534+
}
472535
}
473536

474537
Ok(())

tests/misc_testsuite/threads/LB.wast

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
(module $Mem
2+
(memory (export "shared") 1 1 shared)
3+
)
4+
5+
(thread $T1 (shared (module $Mem))
6+
(register "mem" $Mem)
7+
(module
8+
(memory (import "mem" "shared") 1 10 shared)
9+
(func (export "run")
10+
(local i32)
11+
(i32.load (i32.const 4))
12+
(local.set 0)
13+
(i32.store (i32.const 0) (i32.const 1))
14+
15+
;; store results for checking
16+
(i32.store (i32.const 24) (local.get 0))
17+
)
18+
)
19+
(invoke "run")
20+
)
21+
22+
(thread $T2 (shared (module $Mem))
23+
(register "mem" $Mem)
24+
(module
25+
(memory (import "mem" "shared") 1 1 shared)
26+
(func (export "run")
27+
(local i32)
28+
(i32.load (i32.const 0))
29+
(local.set 0)
30+
(i32.store (i32.const 4) (i32.const 1))
31+
32+
;; store results for checking
33+
(i32.store (i32.const 32) (local.get 0))
34+
)
35+
)
36+
37+
(invoke "run")
38+
)
39+
40+
(wait $T1)
41+
(wait $T2)
42+
43+
(module $Check
44+
(memory (import "Mem" "shared") 1 1 shared)
45+
46+
(func (export "check") (result i32)
47+
(local i32 i32)
48+
(i32.load (i32.const 24))
49+
(local.set 0)
50+
(i32.load (i32.const 32))
51+
(local.set 1)
52+
53+
;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 1)
54+
55+
(i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
56+
(i32.or (i32.eq (local.get 1) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0)))
57+
(i32.and)
58+
(return)
59+
)
60+
)
61+
62+
(assert_return (invoke $Check "check") (i32.const 1))
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
(module $Mem
2+
(memory (export "shared") 1 1 shared)
3+
)
4+
5+
(thread $T1 (shared (module $Mem))
6+
(register "mem" $Mem)
7+
(module
8+
(memory (import "mem" "shared") 1 10 shared)
9+
(func (export "run")
10+
(local i32)
11+
(i32.atomic.load (i32.const 4))
12+
(local.set 0)
13+
(i32.atomic.store (i32.const 0) (i32.const 1))
14+
15+
;; store results for checking
16+
(i32.store (i32.const 24) (local.get 0))
17+
)
18+
)
19+
(invoke "run")
20+
)
21+
22+
(thread $T2 (shared (module $Mem))
23+
(register "mem" $Mem)
24+
(module
25+
(memory (import "mem" "shared") 1 1 shared)
26+
(func (export "run")
27+
(local i32)
28+
(i32.atomic.load (i32.const 0))
29+
(local.set 0)
30+
(i32.atomic.store (i32.const 4) (i32.const 1))
31+
32+
;; store results for checking
33+
(i32.store (i32.const 32) (local.get 0))
34+
)
35+
)
36+
37+
(invoke "run")
38+
)
39+
40+
(wait $T1)
41+
(wait $T2)
42+
43+
(module $Check
44+
(memory (import "Mem" "shared") 1 1 shared)
45+
46+
(func (export "check") (result i32)
47+
(local i32 i32)
48+
(i32.load (i32.const 24))
49+
(local.set 0)
50+
(i32.load (i32.const 32))
51+
(local.set 1)
52+
53+
;; allowed results: (L_0 = 0 && L_1 = 0) || (L_0 = 0 && L_1 = 1) || (L_0 = 1 && L_1 = 0)
54+
55+
(i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 0)))
56+
(i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 1)))
57+
(i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0)))
58+
(i32.or)
59+
(i32.or)
60+
(return)
61+
)
62+
)
63+
64+
(assert_return (invoke $Check "check") (i32.const 1))

tests/misc_testsuite/threads/MP.wast

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
(module $Mem
2+
(memory (export "shared") 1 1 shared)
3+
)
4+
5+
(thread $T1 (shared (module $Mem))
6+
(register "mem" $Mem)
7+
(module
8+
(memory (import "mem" "shared") 1 10 shared)
9+
(func (export "run")
10+
(i32.store (i32.const 0) (i32.const 42))
11+
(i32.store (i32.const 4) (i32.const 1))
12+
)
13+
)
14+
(invoke "run")
15+
)
16+
17+
(thread $T2 (shared (module $Mem))
18+
(register "mem" $Mem)
19+
(module
20+
(memory (import "mem" "shared") 1 1 shared)
21+
(func (export "run")
22+
(local i32 i32)
23+
(i32.load (i32.const 4))
24+
(local.set 0)
25+
(i32.load (i32.const 0))
26+
(local.set 1)
27+
28+
;; store results for checking
29+
(i32.store (i32.const 24) (local.get 0))
30+
(i32.store (i32.const 32) (local.get 1))
31+
)
32+
)
33+
34+
(invoke "run")
35+
)
36+
37+
(wait $T1)
38+
(wait $T2)
39+
40+
(module $Check
41+
(memory (import "Mem" "shared") 1 1 shared)
42+
43+
(func (export "check") (result i32)
44+
(local i32 i32)
45+
(i32.load (i32.const 24))
46+
(local.set 0)
47+
(i32.load (i32.const 32))
48+
(local.set 1)
49+
50+
;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 42)
51+
52+
(i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
53+
(i32.or (i32.eq (local.get 1) (i32.const 42)) (i32.eq (local.get 1) (i32.const 0)))
54+
(i32.and)
55+
(return)
56+
)
57+
)
58+
59+
(assert_return (invoke $Check "check") (i32.const 1))

0 commit comments

Comments
 (0)