forked from bytecodealliance/wasmtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdiff_wasmi.rs
More file actions
227 lines (208 loc) · 8.28 KB
/
diff_wasmi.rs
File metadata and controls
227 lines (208 loc) · 8.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! Evaluate an exported Wasm function using the wasmi interpreter.
use crate::generators::{DiffValue, DiffValueType, ModuleConfig};
use crate::oracles::engine::{DiffEngine, DiffInstance};
use anyhow::{bail, Context, Error, Result};
use wasmtime::{Trap, TrapCode};
/// A wrapper for `wasmi` as a [`DiffEngine`].
pub struct WasmiEngine;
impl WasmiEngine {
/// Build a new [`WasmiEngine`] but only if the configuration does not rely
/// on features that `wasmi` does not support.
pub fn new(config: &ModuleConfig) -> Result<Self> {
if config.config.reference_types_enabled {
bail!("wasmi does not support reference types")
}
if config.config.simd_enabled {
bail!("wasmi does not support SIMD")
}
if config.config.multi_value_enabled {
bail!("wasmi does not support multi-value")
}
if config.config.saturating_float_to_int_enabled {
bail!("wasmi does not support saturating float-to-int conversions")
}
if config.config.sign_extension_enabled {
bail!("wasmi does not support sign-extension")
}
if config.config.memory64_enabled {
bail!("wasmi does not support memory64");
}
if config.config.bulk_memory_enabled {
bail!("wasmi does not support bulk memory");
}
if config.config.threads_enabled {
bail!("wasmi does not support threads");
}
if config.config.max_memories > 1 {
bail!("wasmi does not support multi-memory");
}
Ok(Self)
}
}
impl DiffEngine for WasmiEngine {
fn name(&self) -> &'static str {
"wasmi"
}
fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
let module = wasmi::Module::from_buffer(wasm).context("unable to validate Wasm module")?;
let instance = wasmi::ModuleInstance::new(&module, &wasmi::ImportsBuilder::default())
.context("unable to instantiate module in wasmi")?;
let instance = instance.run_start(&mut wasmi::NopExternals)?;
Ok(Box::new(WasmiInstance { module, instance }))
}
fn assert_error_match(&self, trap: &Trap, err: &Error) {
// Acquire a `wasmi::Trap` from the wasmi error which we'll use to
// assert that it has the same kind of trap as the wasmtime-based trap.
let wasmi = match err.downcast_ref::<wasmi::Error>() {
Some(wasmi::Error::Trap(trap)) => trap,
// Out-of-bounds data segments turn into this category which
// Wasmtime reports as a `MemoryOutOfBounds`.
Some(wasmi::Error::Memory(msg)) => {
assert_eq!(
trap.trap_code(),
Some(TrapCode::MemoryOutOfBounds),
"wasmtime error did not match wasmi: {msg}"
);
return;
}
// Ignore this for now, looks like "elements segment does not fit"
// falls into this category and to avoid doing string matching this
// is just ignored.
Some(wasmi::Error::Instantiation(msg)) => {
log::debug!("ignoring wasmi instantiation error: {msg}");
return;
}
Some(other) => panic!("unexpected wasmi error: {}", other),
None => err
.downcast_ref::<wasmi::Trap>()
.expect(&format!("not a trap: {:?}", err)),
};
match wasmi.kind() {
wasmi::TrapKind::StackOverflow => {
assert_eq!(trap.trap_code(), Some(TrapCode::StackOverflow))
}
wasmi::TrapKind::MemoryAccessOutOfBounds => {
assert_eq!(trap.trap_code(), Some(TrapCode::MemoryOutOfBounds))
}
wasmi::TrapKind::Unreachable => {
assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached))
}
wasmi::TrapKind::TableAccessOutOfBounds => {
assert_eq!(trap.trap_code(), Some(TrapCode::TableOutOfBounds))
}
wasmi::TrapKind::ElemUninitialized => {
assert_eq!(trap.trap_code(), Some(TrapCode::IndirectCallToNull))
}
wasmi::TrapKind::DivisionByZero => {
assert_eq!(trap.trap_code(), Some(TrapCode::IntegerDivisionByZero))
}
wasmi::TrapKind::IntegerOverflow => {
assert_eq!(trap.trap_code(), Some(TrapCode::IntegerOverflow))
}
wasmi::TrapKind::InvalidConversionToInt => {
assert_eq!(trap.trap_code(), Some(TrapCode::BadConversionToInteger))
}
wasmi::TrapKind::UnexpectedSignature => {
assert_eq!(trap.trap_code(), Some(TrapCode::BadSignature))
}
wasmi::TrapKind::Host(_) => unreachable!(),
}
}
fn is_stack_overflow(&self, err: &Error) -> bool {
let trap = match err.downcast_ref::<wasmi::Error>() {
Some(wasmi::Error::Trap(trap)) => trap,
Some(_) => return false,
None => match err.downcast_ref::<wasmi::Trap>() {
Some(trap) => trap,
None => return false,
},
};
match trap.kind() {
wasmi::TrapKind::StackOverflow => true,
_ => false,
}
}
}
/// A wrapper for `wasmi` Wasm instances.
struct WasmiInstance {
#[allow(dead_code)] // reason = "the module must live as long as its reference"
module: wasmi::Module,
instance: wasmi::ModuleRef,
}
impl DiffInstance for WasmiInstance {
fn name(&self) -> &'static str {
"wasmi"
}
fn evaluate(
&mut self,
function_name: &str,
arguments: &[DiffValue],
_results: &[DiffValueType],
) -> Result<Option<Vec<DiffValue>>> {
let arguments: Vec<_> = arguments.iter().map(wasmi::RuntimeValue::from).collect();
let export = self
.instance
.export_by_name(function_name)
.context(format!(
"unable to find function '{}' in wasmi instance",
function_name
))?;
let function = export.as_func().context("wasmi export is not a function")?;
let result = wasmi::FuncInstance::invoke(&function, &arguments, &mut wasmi::NopExternals)
.context("failed while invoking function in wasmi")?;
Ok(Some(if let Some(result) = result {
vec![result.into()]
} else {
vec![]
}))
}
fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
match self.instance.export_by_name(name) {
Some(wasmi::ExternVal::Global(g)) => Some(g.get().into()),
_ => unreachable!(),
}
}
fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
assert!(!shared);
match self.instance.export_by_name(name) {
Some(wasmi::ExternVal::Memory(m)) => {
// `wasmi` memory may be stored non-contiguously; copy
// it out to a contiguous chunk.
let mut buffer: Vec<u8> = vec![0; m.current_size().0 * 65536];
m.get_into(0, &mut buffer[..])
.expect("can access wasmi memory");
Some(buffer)
}
_ => unreachable!(),
}
}
}
impl From<&DiffValue> for wasmi::RuntimeValue {
fn from(v: &DiffValue) -> Self {
use wasmi::RuntimeValue::*;
match *v {
DiffValue::I32(n) => I32(n),
DiffValue::I64(n) => I64(n),
DiffValue::F32(n) => F32(wasmi::nan_preserving_float::F32::from_bits(n)),
DiffValue::F64(n) => F64(wasmi::nan_preserving_float::F64::from_bits(n)),
DiffValue::V128(_) | DiffValue::FuncRef { .. } | DiffValue::ExternRef { .. } => {
unimplemented!()
}
}
}
}
impl Into<DiffValue> for wasmi::RuntimeValue {
fn into(self) -> DiffValue {
use wasmi::RuntimeValue::*;
match self {
I32(n) => DiffValue::I32(n),
I64(n) => DiffValue::I64(n),
F32(n) => DiffValue::F32(n.to_bits()),
F64(n) => DiffValue::F64(n.to_bits()),
}
}
}
#[test]
fn smoke() {
crate::oracles::engine::smoke_test_engine(|config| WasmiEngine::new(&config.module_config))
}