Path: blob/main/crates/fuzzing/src/oracles/diff_wasmtime.rs
1693 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 anyhow::{Context, Error, Result};8use arbitrary::Unstructured;9use wasmtime::{Extern, FuncType, Instance, Module, Store, Trap, Val};1011/// A wrapper for using Wasmtime as a [`DiffEngine`].12pub struct WasmtimeEngine {13config: generators::Config,14}1516impl WasmtimeEngine {17/// Merely store the configuration; the engine is actually constructed18/// later. Ideally the store and engine could be built here but19/// `compile_module` takes a [`generators::Config`]; TODO re-factor this if20/// that ever changes.21pub fn new(22u: &mut Unstructured<'_>,23config: &mut generators::Config,24compiler_strategy: CompilerStrategy,25) -> arbitrary::Result<Self> {26let mut new_config = u.arbitrary::<WasmtimeConfig>()?;27new_config.compiler_strategy = compiler_strategy;28new_config.update_module_config(&mut config.module_config, u)?;29new_config.make_compatible_with(&config.wasmtime);3031let config = generators::Config {32wasmtime: new_config,33module_config: config.module_config.clone(),34};35log::debug!("Created new Wasmtime differential engine with config: {config:?}");3637Ok(Self { config })38}39}4041impl DiffEngine for WasmtimeEngine {42fn name(&self) -> &'static str {43match self.config.wasmtime.compiler_strategy {44CompilerStrategy::CraneliftNative => "wasmtime",45CompilerStrategy::Winch => "winch",46CompilerStrategy::CraneliftPulley => "pulley",47}48}4950fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {51let store = self.config.to_store();52let module = compile_module(store.engine(), wasm, KnownValid::Yes, &self.config).unwrap();53let instance = WasmtimeInstance::new(store, module)?;54Ok(Box::new(instance))55}5657fn assert_error_match(&self, lhs: &Error, rhs: &Trap) {58let lhs = lhs59.downcast_ref::<Trap>()60.expect(&format!("not a trap: {lhs:?}"));6162assert_eq!(lhs, rhs, "{lhs}\nis not equal to\n{rhs}");63}6465fn is_non_deterministic_error(&self, err: &Error) -> bool {66match err.downcast_ref::<Trap>() {67Some(trap) => super::wasmtime_trap_is_non_deterministic(trap),68None => false,69}70}71}7273/// A wrapper around a Wasmtime instance.74///75/// The Wasmtime engine constructs a new store and compiles an instance of a76/// Wasm module.77pub struct WasmtimeInstance {78store: Store<StoreLimits>,79instance: Instance,80}8182impl WasmtimeInstance {83/// Instantiate a new Wasmtime instance.84pub fn new(mut store: Store<StoreLimits>, module: Module) -> Result<Self> {85let instance = dummy::dummy_linker(&mut store, &module)86.and_then(|l| l.instantiate(&mut store, &module))87.context("unable to instantiate module in wasmtime")?;88Ok(Self { store, instance })89}9091/// Retrieve the names and types of all exported functions in the instance.92///93/// This is useful for evaluating each exported function with different94/// values. The [`DiffInstance`] trait asks for the function name and we95/// need to know the function signature in order to pass in the right96/// arguments.97pub fn exported_functions(&mut self) -> Vec<(String, FuncType)> {98let exported_functions = self99.instance100.exports(&mut self.store)101.map(|e| (e.name().to_owned(), e.into_func()))102.filter_map(|(n, f)| f.map(|f| (n, f)))103.collect::<Vec<_>>();104exported_functions105.into_iter()106.map(|(n, f)| (n, f.ty(&self.store)))107.collect()108}109110/// Returns the list of globals and their types exported from this instance.111pub fn exported_globals(&mut self) -> Vec<(String, DiffValueType)> {112let globals = self113.instance114.exports(&mut self.store)115.filter_map(|e| {116let name = e.name();117e.into_global().map(|g| (name.to_string(), g))118})119.collect::<Vec<_>>();120121globals122.into_iter()123.filter_map(|(name, global)| {124DiffValueType::try_from(global.ty(&self.store).content().clone())125.map(|ty| (name, ty))126.ok()127})128.collect()129}130131/// Returns the list of exported memories and whether or not it's a shared132/// memory.133pub fn exported_memories(&mut self) -> Vec<(String, bool)> {134self.instance135.exports(&mut self.store)136.filter_map(|e| {137let name = e.name();138match e.into_extern() {139Extern::Memory(_) => Some((name.to_string(), false)),140Extern::SharedMemory(_) => Some((name.to_string(), true)),141_ => None,142}143})144.collect()145}146147/// Returns whether or not this instance has hit its OOM condition yet.148pub fn is_oom(&self) -> bool {149self.store.data().is_oom()150}151}152153impl DiffInstance for WasmtimeInstance {154fn name(&self) -> &'static str {155"wasmtime"156}157158fn evaluate(159&mut self,160function_name: &str,161arguments: &[DiffValue],162_results: &[DiffValueType],163) -> Result<Option<Vec<DiffValue>>> {164let arguments: Vec<_> = arguments.iter().map(Val::from).collect();165166let function = self167.instance168.get_func(&mut self.store, function_name)169.expect("unable to access exported function");170let ty = function.ty(&self.store);171let mut results = vec![Val::I32(0); ty.results().len()];172function.call(&mut self.store, &arguments, &mut results)?;173174let results = results.into_iter().map(Val::into).collect();175Ok(Some(results))176}177178fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {179Some(180self.instance181.get_global(&mut self.store, name)182.unwrap()183.get(&mut self.store)184.into(),185)186}187188fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {189Some(if shared {190let memory = self191.instance192.get_shared_memory(&mut self.store, name)193.unwrap();194memory.data().iter().map(|i| unsafe { *i.get() }).collect()195} else {196self.instance197.get_memory(&mut self.store, name)198.unwrap()199.data(&self.store)200.to_vec()201})202}203}204205impl From<&DiffValue> for Val {206fn from(v: &DiffValue) -> Self {207match *v {208DiffValue::I32(n) => Val::I32(n),209DiffValue::I64(n) => Val::I64(n),210DiffValue::F32(n) => Val::F32(n),211DiffValue::F64(n) => Val::F64(n),212DiffValue::V128(n) => Val::V128(n.into()),213DiffValue::FuncRef { null } => {214assert!(null);215Val::FuncRef(None)216}217DiffValue::ExternRef { null } => {218assert!(null);219Val::ExternRef(None)220}221DiffValue::AnyRef { null } => {222assert!(null);223Val::AnyRef(None)224}225DiffValue::ExnRef { null } => {226assert!(null);227Val::ExnRef(None)228}229DiffValue::ContRef { null } => {230assert!(null);231Val::ExnRef(None)232}233}234}235}236237impl From<Val> for DiffValue {238fn from(val: Val) -> DiffValue {239match val {240Val::I32(n) => DiffValue::I32(n),241Val::I64(n) => DiffValue::I64(n),242Val::F32(n) => DiffValue::F32(n),243Val::F64(n) => DiffValue::F64(n),244Val::V128(n) => DiffValue::V128(n.into()),245Val::ExternRef(r) => DiffValue::ExternRef { null: r.is_none() },246Val::FuncRef(r) => DiffValue::FuncRef { null: r.is_none() },247Val::AnyRef(r) => DiffValue::AnyRef { null: r.is_none() },248Val::ExnRef(e) => DiffValue::ExnRef { null: e.is_none() },249Val::ContRef(c) => DiffValue::ContRef { null: c.is_none() },250}251}252}253254#[cfg(test)]255mod tests {256use super::*;257258#[test]259fn smoke_cranelift_native() {260crate::oracles::engine::smoke_test_engine(|u, config| {261WasmtimeEngine::new(u, config, CompilerStrategy::CraneliftNative)262})263}264265#[test]266fn smoke_cranelift_pulley() {267crate::oracles::engine::smoke_test_engine(|u, config| {268WasmtimeEngine::new(u, config, CompilerStrategy::CraneliftPulley)269})270}271272#[test]273fn smoke_winch() {274if !cfg!(target_arch = "x86_64") {275return;276}277crate::oracles::engine::smoke_test_engine(|u, config| {278WasmtimeEngine::new(u, config, CompilerStrategy::Winch)279})280}281}282283284