Path: blob/main/crates/fuzzing/src/oracles/diff_wasmtime.rs
3067 views
//! Evaluate an exported Wasm function using Wasmtime.12use crate::generators::{self, CompilerStrategy, DiffValue, DiffValueType, WasmtimeConfig};3use crate::oracles::dummy;4use crate::oracles::engine::DiffInstance;5use crate::oracles::{StoreLimits, compile_module, engine::DiffEngine};6use crate::single_module_fuzzer::KnownValid;7use arbitrary::Unstructured;8use wasmtime::{9Error, Extern, FuncType, Instance, Module, Result, Store, Trap, Val, error::Context as _,10};1112/// A wrapper for using Wasmtime as a [`DiffEngine`].13pub struct WasmtimeEngine {14config: generators::Config,15}1617impl WasmtimeEngine {18/// Merely store the configuration; the engine is actually constructed19/// later. Ideally the store and engine could be built here but20/// `compile_module` takes a [`generators::Config`]; TODO re-factor this if21/// that ever changes.22pub fn new(23u: &mut Unstructured<'_>,24config: &mut generators::Config,25compiler_strategy: CompilerStrategy,26) -> arbitrary::Result<Self> {27let mut new_config = u.arbitrary::<WasmtimeConfig>()?;28new_config.compiler_strategy = compiler_strategy;29new_config.update_module_config(&mut config.module_config, u)?;30new_config.make_compatible_with(&config.wasmtime);3132let config = generators::Config {33wasmtime: new_config,34module_config: config.module_config.clone(),35};36log::debug!("Created new Wasmtime differential engine with config: {config:?}");3738Ok(Self { config })39}40}4142impl DiffEngine for WasmtimeEngine {43fn name(&self) -> &'static str {44match self.config.wasmtime.compiler_strategy {45CompilerStrategy::CraneliftNative => "wasmtime",46CompilerStrategy::Winch => "winch",47CompilerStrategy::CraneliftPulley => "pulley",48}49}5051fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {52let store = self.config.to_store();53let module = compile_module(store.engine(), wasm, KnownValid::Yes, &self.config).unwrap();54let instance = WasmtimeInstance::new(store, module)?;55Ok(Box::new(instance))56}5758fn assert_error_match(&self, lhs: &Error, rhs: &Trap) {59let lhs = lhs60.downcast_ref::<Trap>()61.expect(&format!("not a trap: {lhs:?}"));6263assert_eq!(lhs, rhs, "{lhs}\nis not equal to\n{rhs}");64}6566fn is_non_deterministic_error(&self, err: &Error) -> bool {67match err.downcast_ref::<Trap>() {68Some(trap) => super::wasmtime_trap_is_non_deterministic(trap),69None => false,70}71}72}7374/// A wrapper around a Wasmtime instance.75///76/// The Wasmtime engine constructs a new store and compiles an instance of a77/// Wasm module.78pub struct WasmtimeInstance {79store: Store<StoreLimits>,80instance: Instance,81}8283impl WasmtimeInstance {84/// Instantiate a new Wasmtime instance.85pub fn new(mut store: Store<StoreLimits>, module: Module) -> Result<Self> {86let instance = dummy::dummy_linker(&mut store, &module)87.and_then(|l| l.instantiate(&mut store, &module))88.context("unable to instantiate module in wasmtime")?;89Ok(Self { store, instance })90}9192/// Retrieve the names and types of all exported functions in the instance.93///94/// This is useful for evaluating each exported function with different95/// values. The [`DiffInstance`] trait asks for the function name and we96/// need to know the function signature in order to pass in the right97/// arguments.98pub fn exported_functions(&mut self) -> Vec<(String, FuncType)> {99let exported_functions = self100.instance101.exports(&mut self.store)102.map(|e| (e.name().to_owned(), e.into_func()))103.filter_map(|(n, f)| f.map(|f| (n, f)))104.collect::<Vec<_>>();105exported_functions106.into_iter()107.map(|(n, f)| (n, f.ty(&self.store)))108.collect()109}110111/// Returns the list of globals and their types exported from this instance.112pub fn exported_globals(&mut self) -> Vec<(String, DiffValueType)> {113let globals = self114.instance115.exports(&mut self.store)116.filter_map(|e| {117let name = e.name();118e.into_global().map(|g| (name.to_string(), g))119})120.collect::<Vec<_>>();121122globals123.into_iter()124.filter_map(|(name, global)| {125DiffValueType::try_from(global.ty(&self.store).content().clone())126.map(|ty| (name, ty))127.ok()128})129.collect()130}131132/// Returns the list of exported memories and whether or not it's a shared133/// memory.134pub fn exported_memories(&mut self) -> Vec<(String, bool)> {135self.instance136.exports(&mut self.store)137.filter_map(|e| {138let name = e.name();139match e.into_extern() {140Extern::Memory(_) => Some((name.to_string(), false)),141Extern::SharedMemory(_) => Some((name.to_string(), true)),142_ => None,143}144})145.collect()146}147148/// Returns whether or not this instance has hit its OOM condition yet.149pub fn is_oom(&self) -> bool {150self.store.data().is_oom()151}152}153154impl DiffInstance for WasmtimeInstance {155fn name(&self) -> &'static str {156"wasmtime"157}158159fn evaluate(160&mut self,161function_name: &str,162arguments: &[DiffValue],163_results: &[DiffValueType],164) -> Result<Option<Vec<DiffValue>>> {165let arguments: Vec<_> = arguments.iter().map(Val::from).collect();166167let function = self168.instance169.get_func(&mut self.store, function_name)170.expect("unable to access exported function");171let ty = function.ty(&self.store);172let mut results = vec![Val::I32(0); ty.results().len()];173function.call(&mut self.store, &arguments, &mut results)?;174175let results = results.into_iter().map(Val::into).collect();176Ok(Some(results))177}178179fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {180Some(181self.instance182.get_global(&mut self.store, name)183.unwrap()184.get(&mut self.store)185.into(),186)187}188189fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {190Some(if shared {191let memory = self192.instance193.get_shared_memory(&mut self.store, name)194.unwrap();195memory.data().iter().map(|i| unsafe { *i.get() }).collect()196} else {197self.instance198.get_memory(&mut self.store, name)199.unwrap()200.data(&self.store)201.to_vec()202})203}204}205206impl From<&DiffValue> for Val {207fn from(v: &DiffValue) -> Self {208match *v {209DiffValue::I32(n) => Val::I32(n),210DiffValue::I64(n) => Val::I64(n),211DiffValue::F32(n) => Val::F32(n),212DiffValue::F64(n) => Val::F64(n),213DiffValue::V128(n) => Val::V128(n.into()),214DiffValue::FuncRef { null } => {215assert!(null);216Val::FuncRef(None)217}218DiffValue::ExternRef { null } => {219assert!(null);220Val::ExternRef(None)221}222DiffValue::AnyRef { null } => {223assert!(null);224Val::AnyRef(None)225}226DiffValue::ExnRef { null } => {227assert!(null);228Val::ExnRef(None)229}230DiffValue::ContRef { null } => {231assert!(null);232Val::ExnRef(None)233}234}235}236}237238impl From<Val> for DiffValue {239fn from(val: Val) -> DiffValue {240match val {241Val::I32(n) => DiffValue::I32(n),242Val::I64(n) => DiffValue::I64(n),243Val::F32(n) => DiffValue::F32(n),244Val::F64(n) => DiffValue::F64(n),245Val::V128(n) => DiffValue::V128(n.into()),246Val::ExternRef(r) => DiffValue::ExternRef { null: r.is_none() },247Val::FuncRef(r) => DiffValue::FuncRef { null: r.is_none() },248Val::AnyRef(r) => DiffValue::AnyRef { null: r.is_none() },249Val::ExnRef(e) => DiffValue::ExnRef { null: e.is_none() },250Val::ContRef(c) => DiffValue::ContRef { null: c.is_none() },251}252}253}254255#[cfg(test)]256mod tests {257use super::*;258259#[test]260fn smoke_cranelift_native() {261crate::oracles::engine::smoke_test_engine(|u, config| {262WasmtimeEngine::new(u, config, CompilerStrategy::CraneliftNative)263})264}265266#[test]267fn smoke_cranelift_pulley() {268crate::oracles::engine::smoke_test_engine(|u, config| {269WasmtimeEngine::new(u, config, CompilerStrategy::CraneliftPulley)270})271}272273#[test]274fn smoke_winch() {275if !cfg!(target_arch = "x86_64") {276return;277}278crate::oracles::engine::smoke_test_engine(|u, config| {279WasmtimeEngine::new(u, config, CompilerStrategy::Winch)280})281}282}283284285