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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
use super::create_module;
use crate::{
core::UntypedVal,
engine::{bytecode::Instruction, DedupFuncType, EngineFunc},
Config,
Engine,
Module,
};
use core::sync::atomic::Ordering;
use std::{boxed::Box, sync::atomic::AtomicBool, vec::Vec};
/// A test driver for translation tests.
#[derive(Debug)]
pub struct TranslationTest {
/// The input Wasm bytes.
wasm: Box<[u8]>,
/// The config under which the engine is tested.
config: Config,
/// The expected functions and their instructions.
expected_funcs: Vec<ExpectedFunc>,
/// Is `true` if the [`TranslationTest`] has been run at least once.
has_run: AtomicBool,
}
impl Drop for TranslationTest {
fn drop(&mut self) {
if !self.has_run.load(Ordering::SeqCst) {
panic!("TranslationTest did not run at least once. This is probably a bug!")
}
}
}
/// An entry for an expected function body stored in the engine under test.
#[derive(Debug, Clone)]
pub struct ExpectedFunc {
/// The instructions of the expected function.
instrs: Vec<Instruction>,
/// The function local constant values.
consts: Vec<UntypedVal>,
}
impl ExpectedFunc {
/// Create a new [`ExpectedFunc`] with the given Wasmi bytecode [`Instruction`] sequence.
pub fn new<I>(instrs: I) -> Self
where
I: IntoIterator<Item = Instruction>,
{
let instrs: Vec<_> = instrs.into_iter().collect();
assert!(
!instrs.is_empty(),
"an expected function must have instructions"
);
Self {
instrs,
consts: Vec::new(),
}
}
/// Add expected function local constant values to this [`ExpectedFunc`].
///
/// # Note
///
/// The function local constant values are in the order of their expected
/// and deduplicated allocations during the translation phase.
///
/// # Panics
///
/// - If the `consts` iterator yields no values.
/// - If this [`ExpectedFunc`] already expects some function local constant values.
pub fn consts<I, T>(mut self, consts: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<UntypedVal>,
{
assert!(
self.expected_consts().is_empty(),
"tried to call `expect_consts` twice"
);
self.consts.extend(consts.into_iter().map(Into::into));
assert!(
!self.expected_consts().is_empty(),
"called `expect_consts` with empty set"
);
self
}
/// Returns the expected [`Instruction`] sequence of the [`ExpectedFunc`] as slice.
fn expected_instrs(&self) -> &[Instruction] {
&self.instrs
}
/// Returns the expected function local constant values of the [`ExpectedFunc`] as slice.
fn expected_consts(&self) -> &[UntypedVal] {
&self.consts
}
/// Asserts that properties of the [`ExpectedFunc`] have been translated as expected.
fn assert_func(&self, engine: &Engine, func_type: DedupFuncType, engine_func: EngineFunc) {
self.assert_instrs(engine, engine_func, func_type);
self.assert_consts(engine, engine_func);
}
/// Asserts that the instructions of the [`ExpectedFunc`] have been translated as expected.
fn assert_instrs(&self, engine: &Engine, engine_func: EngineFunc, func_type: DedupFuncType) {
let expected_instrs = self.expected_instrs();
let len_expected = expected_instrs.len();
let func_type = engine.resolve_func_type(&func_type, Clone::clone);
for (index, actual, expected) in
expected_instrs
.iter()
.copied()
.enumerate()
.map(|(index, expected_instr)| {
let actual_instr =
engine
.resolve_instr(engine_func, index)
.unwrap_or_else(|error| panic!("failed to compiled lazily initialized function: {}", error))
.unwrap_or_else(|| {
panic!("missing instruction at index {index} for {engine_func:?} ({func_type:?})")
});
(index, actual_instr, expected_instr)
})
{
assert!(
actual == expected,
"instruction mismatch at index {index} for {engine_func:?} ({func_type:?})\n \
- expected: {expected:?}\n \
- found: {actual:?}",
);
}
if let Ok(Some(unexpected)) = engine.resolve_instr(engine_func, len_expected) {
panic!("unexpected instruction at index {len_expected}: {unexpected:?}");
}
}
/// Asserts that the function local constant values of the [`ExpectedFunc`] have been translated as expected.
fn assert_consts(&self, engine: &Engine, func: EngineFunc) {
let expected_consts = self.expected_consts();
for (index, expected_value) in expected_consts.iter().copied().enumerate() {
let actual_value = engine
.get_func_const(func, index)
.unwrap_or_else(|error| {
panic!("failed to compiled lazily initialized function: {}", error)
})
.unwrap_or_else(|| {
panic!("missing function local constant value of for {func:?} at index {index}")
});
assert_eq!(
actual_value, expected_value,
"function local constant value mismatch for {func:?} at index {index}"
);
}
// Check that there are not more function local constants than we already expected.
let len_consts = expected_consts.len();
if let Ok(Some(unexpected)) = engine.get_func_const(func, len_consts) {
panic!("unexpected function local constant value (= {unexpected:?}) for {func:?} at index {len_consts}")
}
}
}
impl TranslationTest {
/// Creates a new [`TranslationTest`] for the given Webassembly `bytes`.
///
/// # Panics
///
/// If the WebAssembly `bytes` is not valid WebAssembly.
#[must_use]
fn new(bytes: impl AsRef<[u8]>) -> Self {
let config = {
let mut cfg = Config::default();
cfg.wasm_tail_call(true);
cfg
};
Self {
wasm: bytes.as_ref().into(),
config,
expected_funcs: Vec::new(),
has_run: AtomicBool::from(false),
}
}
/// Creates a new [`TranslationTest`] for the given Webassembly `source`.
///
/// # Panics
///
/// If the WebAssembly `source` is not valid WebAssembly Text Format (WAT).
#[must_use]
pub fn from_wat(source: &str) -> Self {
let wasm = match wat::parse_str(source) {
Ok(wasm) => wasm,
Err(error) => panic!("failed to convert from `.wat` to `.wasm`: {error}"),
};
Self::new(wasm)
}
/// Returns the [`Config`] used for the test case.
fn config(&self) -> &Config {
&self.config
}
/// Returns the WebAssembly bytes used for the test case.
fn wasm(&self) -> &[u8] {
&self.wasm
}
/// Returns the sequence of [`ExpectedFunc`] for the test case.
fn expected_funcs(&self) -> &[ExpectedFunc] {
&self.expected_funcs
}
/// Add an expected function with its instructions.
///
/// # Note
///
/// This is a convenience method for [`TranslationTest::expect_func_ext`].
pub fn expect_func_instrs<I>(&mut self, instrs: I) -> &mut Self
where
I: IntoIterator<Item = Instruction>,
{
self.expect_func(ExpectedFunc::new(instrs))
}
/// Add an [`ExpectedFunc`].
pub fn expect_func(&mut self, func: ExpectedFunc) -> &mut Self {
self.expected_funcs.push(func);
self
}
/// Runs the [`TranslationTest`].
///
/// # Panics
///
/// If the translation test was not successful.
pub fn run(&self) {
self.has_run.store(true, Ordering::SeqCst);
let module = create_module(self.config(), self.wasm());
let engine = module.engine();
self.assert_funcs(engine, &module);
}
/// Asserts that all expected functions of the translated Wasm module are as expected.
fn assert_funcs(&self, engine: &Engine, module: &Module) {
{
let len_compiled = module.internal_funcs().len();
let len_expected = self.expected_funcs().len();
assert_eq!(
len_compiled,
len_expected,
"number of compiled functions (={len_compiled}) do not match the expected amount (= {len_expected})",
);
}
for ((func_type, engine_func), expected_func) in
module.internal_funcs().zip(self.expected_funcs())
{
expected_func.assert_func(engine, func_type, engine_func);
}
}
}