Files
wasmtime/crates/fuzzing/src/oracles/engine.rs
yuyang-ok cdecc858b4 add riscv64 backend for cranelift. (#4271)
Add a RISC-V 64 (`riscv64`, RV64GC) backend.

Co-authored-by: yuyang <756445638@qq.com>
Co-authored-by: Chris Fallin <chris@cfallin.org>
Co-authored-by: Afonso Bordado <afonsobordado@az8.co>
2022-09-27 17:30:31 -07:00

228 lines
8.0 KiB
Rust

//! Define the interface for differential evaluation of Wasm functions.
use crate::generators::{Config, DiffValue, DiffValueType};
use crate::oracles::{diff_wasmi::WasmiEngine, diff_wasmtime::WasmtimeEngine};
use anyhow::Error;
use arbitrary::Unstructured;
use wasmtime::Trap;
/// Returns a function which can be used to build the engine name specified.
///
/// `None` is returned if the named engine does not have support compiled into
/// this crate.
pub fn build(
u: &mut Unstructured<'_>,
name: &str,
config: &mut Config,
) -> arbitrary::Result<Option<Box<dyn DiffEngine>>> {
let engine: Box<dyn DiffEngine> = match name {
"wasmtime" => Box::new(WasmtimeEngine::new(u, config)?),
"wasmi" => Box::new(WasmiEngine::new(config)),
#[cfg(feature = "fuzz-spec-interpreter")]
"spec" => Box::new(crate::oracles::diff_spec::SpecInterpreter::new(config)),
#[cfg(not(feature = "fuzz-spec-interpreter"))]
"spec" => return Ok(None),
#[cfg(not(any(windows, target_arch = "s390x", target_arch = "riscv64")))]
"v8" => Box::new(crate::oracles::diff_v8::V8Engine::new(config)),
#[cfg(any(windows, target_arch = "s390x", target_arch = "riscv64"))]
"v8" => return Ok(None),
_ => panic!("unknown engine {name}"),
};
Ok(Some(engine))
}
/// Provide a way to instantiate Wasm modules.
pub trait DiffEngine {
/// Return the name of the engine.
fn name(&self) -> &'static str;
/// Create a new instance with the given engine.
fn instantiate(&mut self, wasm: &[u8]) -> anyhow::Result<Box<dyn DiffInstance>>;
/// Tests that the wasmtime-originating `trap` matches the error this engine
/// generated.
fn assert_error_match(&self, trap: &Trap, err: &Error);
/// Returns whether the error specified from this engine might be stack
/// overflow.
fn is_stack_overflow(&self, err: &Error) -> bool;
}
/// Provide a way to evaluate Wasm functions--a Wasm instance implemented by a
/// specific engine (i.e., compiler or interpreter).
pub trait DiffInstance {
/// Return the name of the engine behind this instance.
fn name(&self) -> &'static str;
/// Evaluate an exported function with the given values.
///
/// Any error, such as a trap, should be returned through an `Err`. If this
/// engine cannot invoke the function signature then `None` should be
/// returned and this invocation will be skipped.
fn evaluate(
&mut self,
function_name: &str,
arguments: &[DiffValue],
results: &[DiffValueType],
) -> anyhow::Result<Option<Vec<DiffValue>>>;
/// Attempts to return the value of the specified global, returning `None`
/// if this engine doesn't support retrieving globals at this time.
fn get_global(&mut self, name: &str, ty: DiffValueType) -> Option<DiffValue>;
/// Same as `get_global` but for memory.
fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>>;
}
/// Initialize any global state associated with runtimes that may be
/// differentially executed against.
pub fn setup_engine_runtimes() {
#[cfg(feature = "fuzz-spec-interpreter")]
crate::oracles::diff_spec::setup_ocaml_runtime();
}
/// Build a list of allowed values from the given `defaults` using the
/// `env_list`.
///
/// ```
/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;
/// // Passing no `env_list` returns the defaults:
/// assert_eq!(build_allowed_env_list(None, &["a"]), vec!["a"]);
/// // We can build up a subset of the defaults:
/// assert_eq!(build_allowed_env_list(Some(vec!["b".to_string()]), &["a","b"]), vec!["b"]);
/// // Alternately we can subtract from the defaults:
/// assert_eq!(build_allowed_env_list(Some(vec!["-a".to_string()]), &["a","b"]), vec!["b"]);
/// ```
/// ```should_panic
/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;
/// // We are not allowed to mix set "addition" and "subtraction"; the following
/// // will panic:
/// build_allowed_env_list(Some(vec!["-a".to_string(), "b".to_string()]), &["a", "b"]);
/// ```
/// ```should_panic
/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;
/// // This will also panic if invalid values are used:
/// build_allowed_env_list(Some(vec!["c".to_string()]), &["a", "b"]);
/// ```
pub fn build_allowed_env_list<'a>(
env_list: Option<Vec<String>>,
defaults: &[&'a str],
) -> Vec<&'a str> {
if let Some(configured) = &env_list {
// Check that the names are either all additions or all subtractions.
let subtract_from_defaults = configured.iter().all(|c| c.starts_with("-"));
let add_from_defaults = configured.iter().all(|c| !c.starts_with("-"));
let start = if subtract_from_defaults { 1 } else { 0 };
if !subtract_from_defaults && !add_from_defaults {
panic!(
"all configured values must either subtract or add from defaults; found mixed values: {:?}",
&env_list
);
}
// Check that the configured names are valid ones.
for c in configured {
if !defaults.contains(&&c[start..]) {
panic!(
"invalid environment configuration `{}`; must be one of: {:?}",
c, defaults
);
}
}
// Select only the allowed names.
let mut allowed = Vec::with_capacity(defaults.len());
for &d in defaults {
let mentioned = configured.iter().any(|c| &c[start..] == d);
if (add_from_defaults && mentioned) || (subtract_from_defaults && !mentioned) {
allowed.push(d);
}
}
allowed
} else {
defaults.to_vec()
}
}
/// Retrieve a comma-delimited list of values from an environment variable.
pub fn parse_env_list(env_variable: &str) -> Option<Vec<String>> {
std::env::var(env_variable)
.ok()
.map(|l| l.split(",").map(|s| s.to_owned()).collect())
}
#[cfg(test)]
pub fn smoke_test_engine<T>(
mk_engine: impl Fn(&mut arbitrary::Unstructured<'_>, &mut Config) -> arbitrary::Result<T>,
) where
T: DiffEngine,
{
use rand::prelude::*;
let mut rng = SmallRng::seed_from_u64(0);
let mut buf = vec![0; 2048];
let n = 100;
for _ in 0..n {
rng.fill_bytes(&mut buf);
let mut u = Unstructured::new(&buf);
let mut config = match u.arbitrary::<Config>() {
Ok(config) => config,
Err(_) => continue,
};
// This will ensure that wasmtime, which uses this configuration
// settings, can guaranteed instantiate a module.
config.set_differential_config();
let mut engine = match mk_engine(&mut u, &mut config) {
Ok(engine) => engine,
Err(e) => {
println!("skip {:?}", e);
continue;
}
};
let wasm = wat::parse_str(
r#"
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add)
(global (export "global") i32 i32.const 1)
(memory (export "memory") 1)
)
"#,
)
.unwrap();
let mut instance = engine.instantiate(&wasm).unwrap();
let results = instance
.evaluate(
"add",
&[DiffValue::I32(1), DiffValue::I32(2)],
&[DiffValueType::I32],
)
.unwrap();
assert_eq!(results, Some(vec![DiffValue::I32(3)]));
if let Some(val) = instance.get_global("global", DiffValueType::I32) {
assert_eq!(val, DiffValue::I32(1));
}
if let Some(val) = instance.get_memory("memory", false) {
assert_eq!(val.len(), 65536);
for i in val.iter() {
assert_eq!(*i, 0);
}
}
return;
}
panic!("after {n} runs nothing ever ran, something is probably wrong");
}