Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for thread in *.wast tests #7289

Merged
merged 2 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions crates/fuzzing/src/oracles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ use self::engine::{DiffEngine, DiffInstance};
use crate::generators::{self, DiffValue, DiffValueType};
use arbitrary::Arbitrary;
pub use stacks::check_stacks;
use std::cell::Cell;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
use wasmtime::*;
Expand Down Expand Up @@ -62,43 +60,44 @@ pub fn log_wasm(wasm: &[u8]) {
/// The `T` in `Store<T>` for fuzzing stores, used to limit resource
/// consumption during fuzzing.
#[derive(Clone)]
pub struct StoreLimits(Rc<LimitsState>);
pub struct StoreLimits(Arc<LimitsState>);

struct LimitsState {
/// Remaining memory, in bytes, left to allocate
remaining_memory: Cell<usize>,
remaining_memory: AtomicUsize,
/// Whether or not an allocation request has been denied
oom: Cell<bool>,
oom: AtomicBool,
}

impl StoreLimits {
/// Creates the default set of limits for all fuzzing stores.
pub fn new() -> StoreLimits {
StoreLimits(Rc::new(LimitsState {
StoreLimits(Arc::new(LimitsState {
// Limits tables/memories within a store to at most 1gb for now to
// exercise some larger address but not overflow various limits.
remaining_memory: Cell::new(1 << 30),
oom: Cell::new(false),
remaining_memory: AtomicUsize::new(1 << 30),
oom: AtomicBool::new(false),
}))
}

fn alloc(&mut self, amt: usize) -> bool {
log::trace!("alloc {amt:#x} bytes");
match self.0.remaining_memory.get().checked_sub(amt) {
Some(mem) => {
self.0.remaining_memory.set(mem);
true
}
None => {
self.0.oom.set(true);
match self
.0
.remaining_memory
.fetch_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(amt))
{
Ok(_) => true,
Err(_) => {
self.0.oom.store(true, SeqCst);
log::debug!("OOM hit");
false
}
}
}

fn is_oom(&self) -> bool {
self.0.oom.get()
self.0.oom.load(SeqCst)
}
}

Expand Down
103 changes: 83 additions & 20 deletions crates/wast/src/wast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ use crate::component;
use crate::core;
use crate::spectest::*;
use anyhow::{anyhow, bail, Context as _, Error, Result};
use std::collections::HashMap;
use std::path::Path;
use std::str;
use std::thread;
use wasmtime::*;
use wast::lexer::Lexer;
use wast::parser::{self, ParseBuffer};
Expand Down Expand Up @@ -62,7 +64,10 @@ enum Export {
Component(component::Func),
}

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

for directive in ast.directives {
let sp = directive.span();
if log::log_enabled!(log::Level::Debug) {
let (line, col) = sp.linecol_in(wast);
log::debug!("running directive on {}:{}:{}", filename, line + 1, col);
}
self.run_directive(directive)
.map_err(|e| match e.downcast() {
Ok(err) => adjust_wast(err).into(),
Err(e) => e,
})
.with_context(|| {
self.run_directives(ast.directives, filename, wast)
}

fn run_directives(
&mut self,
directives: Vec<WastDirective<'_>>,
filename: &str,
wast: &str,
) -> Result<()> {
let adjust_wast = |mut err: wast::Error| {
err.set_path(filename.as_ref());
err.set_text(wast);
err
};

thread::scope(|scope| {
let mut threads = HashMap::new();
for directive in directives {
let sp = directive.span();
if log::log_enabled!(log::Level::Debug) {
let (line, col) = sp.linecol_in(wast);
format!("failed directive on {}:{}:{}", filename, line + 1, col)
})?;
}
Ok(())
log::debug!("running directive on {}:{}:{}", filename, line + 1, col);
}
self.run_directive(directive, filename, wast, &scope, &mut threads)
.map_err(|e| match e.downcast() {
Ok(err) => adjust_wast(err).into(),
Err(e) => e,
})
.with_context(|| {
let (line, col) = sp.linecol_in(wast);
format!("failed directive on {}:{}:{}", filename, line + 1, col)
})?;
}
Ok(())
})
}

fn run_directive(&mut self, directive: WastDirective) -> Result<()> {
fn run_directive<'a>(
&mut self,
directive: WastDirective<'a>,
filename: &'a str,
wast: &'a str,
scope: &'a thread::Scope<'a, '_>,
threads: &mut HashMap<&'a str, thread::ScopedJoinHandle<'a, Result<()>>>,
) -> Result<()>
where
T: 'a,
{
use wast::WastDirective::*;

match directive {
Expand Down Expand Up @@ -466,9 +499,39 @@ impl<T> WastContext<T> {
}
AssertException { .. } => bail!("unimplemented assert_exception"),

Thread(_) => bail!("unimplemented `thread`"),
Thread(thread) => {
let mut core_linker = Linker::new(self.store.engine());
if let Some(id) = thread.shared_module {
let items = self
.core_linker
.iter(&mut self.store)
.filter(|(module, _, _)| *module == id.name())
.collect::<Vec<_>>();
for (module, name, item) in items {
core_linker.define(&mut self.store, module, name, item)?;
}
}
let mut child_cx = WastContext {
current: None,
core_linker,
#[cfg(feature = "component-model")]
component_linker: component::Linker::new(self.store.engine()),
store: Store::new(self.store.engine(), self.store.data().clone()),
};
let name = thread.name.name();
let child =
scope.spawn(move || child_cx.run_directives(thread.directives, filename, wast));
threads.insert(name, child);
}

Wait { .. } => bail!("unimplemented `wait`"),
Wait { thread, .. } => {
let name = thread.name();
threads
.remove(name)
.ok_or_else(|| anyhow!("no thread named `{name}`"))?
.join()
.unwrap()?;
}
}

Ok(())
Expand Down
62 changes: 62 additions & 0 deletions tests/misc_testsuite/threads/LB.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
(module $Mem
(memory (export "shared") 1 1 shared)
)

(thread $T1 (shared (module $Mem))
(register "mem" $Mem)
(module
(memory (import "mem" "shared") 1 10 shared)
(func (export "run")
(local i32)
(i32.load (i32.const 4))
(local.set 0)
(i32.store (i32.const 0) (i32.const 1))

;; store results for checking
(i32.store (i32.const 24) (local.get 0))
)
)
(invoke "run")
)

(thread $T2 (shared (module $Mem))
(register "mem" $Mem)
(module
(memory (import "mem" "shared") 1 1 shared)
(func (export "run")
(local i32)
(i32.load (i32.const 0))
(local.set 0)
(i32.store (i32.const 4) (i32.const 1))

;; store results for checking
(i32.store (i32.const 32) (local.get 0))
)
)

(invoke "run")
)

(wait $T1)
(wait $T2)

(module $Check
(memory (import "Mem" "shared") 1 1 shared)

(func (export "check") (result i32)
(local i32 i32)
(i32.load (i32.const 24))
(local.set 0)
(i32.load (i32.const 32))
(local.set 1)

;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 1)

(i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
(i32.or (i32.eq (local.get 1) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0)))
(i32.and)
(return)
)
)

(assert_return (invoke $Check "check") (i32.const 1))
64 changes: 64 additions & 0 deletions tests/misc_testsuite/threads/LB_atomic.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
(module $Mem
(memory (export "shared") 1 1 shared)
)

(thread $T1 (shared (module $Mem))
(register "mem" $Mem)
(module
(memory (import "mem" "shared") 1 10 shared)
(func (export "run")
(local i32)
(i32.atomic.load (i32.const 4))
(local.set 0)
(i32.atomic.store (i32.const 0) (i32.const 1))

;; store results for checking
(i32.store (i32.const 24) (local.get 0))
)
)
(invoke "run")
)

(thread $T2 (shared (module $Mem))
(register "mem" $Mem)
(module
(memory (import "mem" "shared") 1 1 shared)
(func (export "run")
(local i32)
(i32.atomic.load (i32.const 0))
(local.set 0)
(i32.atomic.store (i32.const 4) (i32.const 1))

;; store results for checking
(i32.store (i32.const 32) (local.get 0))
)
)

(invoke "run")
)

(wait $T1)
(wait $T2)

(module $Check
(memory (import "Mem" "shared") 1 1 shared)

(func (export "check") (result i32)
(local i32 i32)
(i32.load (i32.const 24))
(local.set 0)
(i32.load (i32.const 32))
(local.set 1)

;; allowed results: (L_0 = 0 && L_1 = 0) || (L_0 = 0 && L_1 = 1) || (L_0 = 1 && L_1 = 0)

(i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 0)))
(i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 1)))
(i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0)))
(i32.or)
(i32.or)
(return)
)
)

(assert_return (invoke $Check "check") (i32.const 1))
59 changes: 59 additions & 0 deletions tests/misc_testsuite/threads/MP.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
(module $Mem
(memory (export "shared") 1 1 shared)
)

(thread $T1 (shared (module $Mem))
(register "mem" $Mem)
(module
(memory (import "mem" "shared") 1 10 shared)
(func (export "run")
(i32.store (i32.const 0) (i32.const 42))
(i32.store (i32.const 4) (i32.const 1))
)
)
(invoke "run")
)

(thread $T2 (shared (module $Mem))
(register "mem" $Mem)
(module
(memory (import "mem" "shared") 1 1 shared)
(func (export "run")
(local i32 i32)
(i32.load (i32.const 4))
(local.set 0)
(i32.load (i32.const 0))
(local.set 1)

;; store results for checking
(i32.store (i32.const 24) (local.get 0))
(i32.store (i32.const 32) (local.get 1))
)
)

(invoke "run")
)

(wait $T1)
(wait $T2)

(module $Check
(memory (import "Mem" "shared") 1 1 shared)

(func (export "check") (result i32)
(local i32 i32)
(i32.load (i32.const 24))
(local.set 0)
(i32.load (i32.const 32))
(local.set 1)

;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 42)

(i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
(i32.or (i32.eq (local.get 1) (i32.const 42)) (i32.eq (local.get 1) (i32.const 0)))
(i32.and)
(return)
)
)

(assert_return (invoke $Check "check") (i32.const 1))
Loading