wain_exec/
runtime.rs

1use crate::cast;
2use crate::globals::Globals;
3use crate::import::{ImportInvalidError, ImportInvokeError, Importer};
4use crate::memory::Memory;
5use crate::stack::{Stack, StackAccess};
6use crate::table::Table;
7use crate::trap::{Result, Trap, TrapReason};
8use crate::value::{Float, LittleEndian, Value};
9use wain_ast as ast;
10use wain_ast::AsValType;
11
12// Note: This implementation currently ignores Wasm's thread model since MVP does not support multiple
13// threads. https://webassembly.github.io/spec/core/exec/runtime.html#configurations
14
15// TODO: Handle external values for imports and exports
16
17// https://webassembly.github.io/spec/core/exec/numerics.html?highlight=ieee#xref-exec-numerics-op-fmin-mathrm-fmin-n-z-1-z-2
18fn fmin<F: Float>(l: F, r: F) -> F {
19    // f32::min() cannot use directly because of NaN handling divergence.
20    // For example, 42f32.min(f32::NAN) is 42 but (f32.min (f32.const 42) (f32.const nan)) is nan.
21    if l.is_nan() {
22        l.to_arithmetic_nan()
23    } else if r.is_nan() {
24        r.to_arithmetic_nan()
25    } else if l == r {
26        F::from_bits(l.to_bits() | r.to_bits())
27    } else {
28        l.min(r)
29    }
30}
31
32// https://webassembly.github.io/spec/core/exec/numerics.html?highlight=ieee#xref-exec-numerics-op-fmax-mathrm-fmax-n-z-1-z-2
33fn fmax<F: Float>(l: F, r: F) -> F {
34    // f32::max() cannot use directly for the same reason as f32::min() and f32.min
35    if l.is_nan() {
36        l.to_arithmetic_nan()
37    } else if r.is_nan() {
38        r.to_arithmetic_nan()
39    } else if l == r {
40        F::from_bits(l.to_bits() & r.to_bits())
41    } else {
42        l.max(r)
43    }
44}
45
46enum ExecState {
47    Breaking(u32), // Breaking
48    Ret,           // Returning from current function call
49    Continue,      // Continuing execution
50}
51
52type ExecResult = Result<ExecState>;
53
54// https://webassembly.github.io/spec/core/exec/runtime.html#module-instances
55pub struct ModuleInstance<'module, 'source> {
56    ast: &'module ast::Module<'source>,
57    table: Table,   // Only one table is allowed for MVP
58    memory: Memory, // Only one memory is allowed for MVP
59    globals: Globals,
60}
61
62// State of abtract machine to run wasm code. This struct contains both store and stack
63// https://webassembly.github.io/spec/core/exec/runtime.html
64pub struct Runtime<'module, 'source, I: Importer> {
65    module: ModuleInstance<'module, 'source>,
66    stack: Stack,
67    importer: I,
68}
69
70impl<'m, 's, I: Importer> Runtime<'m, 's, I> {
71    /// Initialize states of execution (stack, memory, ...) and instantiate a given module. It means
72    /// that 'start function' is invoked in this function if presents.
73    /// The module is assumed to be validated. If an invalid module is given, the behavior is
74    /// unspecified, meaning that it may crash or the result may be incorrect.
75    ///
76    /// https://webassembly.github.io/spec/core/exec/modules.html#instantiation
77    pub fn instantiate(module: &'m ast::Module<'s>, importer: I) -> Result<Self> {
78        // TODO: 2., 3., 4. Validate external values before instantiate globals
79
80        fn unknown_import(import: &ast::Import<'_>, at: usize) -> Box<Trap> {
81            Trap::new(
82                TrapReason::UnknownImport {
83                    mod_name: import.mod_name.0.to_string(),
84                    name: import.name.0.to_string(),
85                    kind: "function",
86                },
87                at,
88            )
89        }
90
91        for func in module.funcs.iter() {
92            match &func.kind {
93                ast::FuncKind::Body { .. } => break, // All imports precedes other definitions
94                ast::FuncKind::Import(i) => {
95                    let mod_name = &i.mod_name.0;
96                    if mod_name != I::MODULE_NAME {
97                        return Err(unknown_import(i, func.start));
98                    }
99
100                    let fty = &module.types[func.idx as usize];
101                    let name = &i.name.0;
102                    match importer.validate(name, &fty.params, fty.results.get(0).copied()) {
103                        Some(ImportInvalidError::NotFound) => {
104                            return Err(unknown_import(i, func.start));
105                        }
106                        Some(ImportInvalidError::SignatureMismatch {
107                            expected_params,
108                            expected_ret,
109                        }) => {
110                            return Err(Trap::new(
111                                TrapReason::FuncSignatureMismatch {
112                                    import: Some((mod_name.to_string(), name.to_string())),
113                                    expected_params: expected_params.to_vec(),
114                                    expected_results: expected_ret.into_iter().collect(),
115                                    actual_params: fty.params.to_vec(),
116                                    actual_results: fty.results.clone(),
117                                },
118                                func.start,
119                            ))
120                        }
121                        None => { /* do nothing */ }
122                    }
123                }
124            }
125        }
126
127        // 5. global initialization values determined by module and externval
128        let globals = Globals::instantiate(&module.globals)?;
129
130        // 6. a new module instance allocated from module in store S
131        // https://webassembly.github.io/spec/core/exec/modules.html#alloc-module
132
133        // 6.2 allocate functions (nothing to do since we run abstract tree directly)
134
135        // 6.3 allocate table
136        let mut table = Table::allocate(&module.tables)?;
137        // 6.4 allocate memory
138        let mut memory = Memory::allocate(&module.memories)?;
139
140        // 7. and 8. push empty frame
141        let stack = Stack::default();
142
143        // 9. add element segments to table
144        for elem in module.elems.iter() {
145            table.new_elem(elem, &globals)?;
146        }
147
148        // 10. add data segments to memory
149        for data in module.data.iter() {
150            memory.new_data(data, &globals)?;
151        }
152
153        // 11. and 12. pop frame (unnecessary for now)
154
155        let mut runtime = Self {
156            module: ModuleInstance {
157                ast: module,
158                table,
159                memory,
160                globals,
161            },
162            stack,
163            importer,
164        };
165
166        // 15. If the start function is not empty, invoke it
167        if let Some(start) = &runtime.module.ast.entrypoint {
168            // Execute entrypoint
169            runtime.invoke_by_funcidx(start.idx)?;
170        }
171
172        Ok(runtime)
173    }
174
175    pub fn module(&self) -> &'m ast::Module<'s> {
176        self.module.ast
177    }
178
179    pub fn memory(&self) -> &Memory {
180        &self.module.memory
181    }
182
183    pub fn get_global(&self, name: &str) -> Option<Value> {
184        self.module
185            .ast
186            .exports
187            .iter()
188            .find_map(|e| match e.kind {
189                ast::ExportKind::Global(idx) if e.name.0 == name => Some(idx),
190                _ => None,
191            })
192            .map(|idx| {
193                let ty = self.module.ast.globals[idx as usize].ty;
194                self.module.globals.get_any(idx, ty)
195            })
196    }
197
198    // Returns if it has return value on stack or not
199    fn invoke_import(&mut self, import: &ast::Import<'s>, has_ret: bool, pos: usize) -> Result<bool> {
200        if import.mod_name.0 == I::MODULE_NAME {
201            match self
202                .importer
203                .call(&import.name.0, &mut self.stack, &mut self.module.memory)
204            {
205                Ok(()) => return Ok(has_ret),
206                Err(ImportInvokeError::Fatal { message }) => {
207                    return Err(Trap::new(
208                        TrapReason::ImportFuncCallFail {
209                            mod_name: import.mod_name.0.to_string(),
210                            name: import.name.0.to_string(),
211                            msg: message,
212                        },
213                        pos,
214                    ))
215                }
216            }
217        }
218        unreachable!(
219            "fatal: invalid import at runtime: {}::{}",
220            import.mod_name.0, import.name.0
221        );
222    }
223
224    // https://webassembly.github.io/spec/core/exec/modules.html#invocation
225    // https://webassembly.github.io/spec/core/exec/instructions.html#function-calls
226    // Returns if it has return value on stack or not
227    fn invoke_by_funcidx(&mut self, funcidx: u32) -> Result<bool> {
228        let func = &self.module.ast.funcs[funcidx as usize];
229        let fty = &self.module.ast.types[func.idx as usize];
230
231        // Call this function with params
232        let (locals, body) = match &func.kind {
233            ast::FuncKind::Import(i) => return self.invoke_import(i, !fty.results.is_empty(), func.start),
234            ast::FuncKind::Body { locals, expr } => (locals, expr),
235        };
236
237        let prev_frame = self.stack.push_frame(&fty.params, locals);
238
239        for insn in body {
240            match insn.execute(self)? {
241                ExecState::Continue => {}
242                // When using br or br_if outside control instructions, it unwinds execution in
243                // the function body. Label with empty continuation is put before invoking the
244                // function body (11.). It means that breaking outside control instructions will be
245                // caught by this label.
246                ExecState::Ret | ExecState::Breaking(_) => break,
247            }
248        }
249
250        let has_result = !fty.results.is_empty();
251        self.stack.pop_frame(prev_frame, has_result);
252        Ok(has_result)
253    }
254
255    // Invoke function by name
256    pub fn invoke(&mut self, name: impl AsRef<str>, args: &[Value]) -> Result<Option<Value>> {
257        fn find_func_to_invoke(name: &str, exports: &[ast::Export<'_>]) -> Result<(u32, usize)> {
258            for export in exports {
259                if export.name.0 == name {
260                    let actual = match export.kind {
261                        ast::ExportKind::Func(idx) => return Ok((idx, export.start)),
262                        ast::ExportKind::Table(_) => "table",
263                        ast::ExportKind::Memory(_) => "memory",
264                        ast::ExportKind::Global(_) => "global variable",
265                    };
266                    return Err(Trap::new(
267                        TrapReason::WrongInvokeTarget {
268                            name: name.to_string(),
269                            actual: Some(actual),
270                        },
271                        export.start,
272                    ));
273                }
274            }
275            Err(Trap::new(
276                TrapReason::WrongInvokeTarget {
277                    name: name.to_string(),
278                    actual: None,
279                },
280                0,
281            ))
282        }
283
284        let name = name.as_ref();
285        let (funcidx, start) = find_func_to_invoke(name, &self.module.ast.exports)?;
286        let arg_types = &self.module.ast.types[self.module.ast.funcs[funcidx as usize].idx as usize].params;
287
288        // Check parameter types
289        if args.iter().map(Value::valtype).ne(arg_types.iter().copied()) {
290            return Err(Trap::new(
291                TrapReason::InvokeInvalidArgs {
292                    name: name.to_string(),
293                    args: args.to_vec(),
294                    arg_types: arg_types.to_vec(),
295                },
296                start,
297            ));
298        }
299
300        // Push values to stack for invoking the function
301        for arg in args {
302            self.stack.push(arg.clone());
303        }
304
305        if self.invoke_by_funcidx(funcidx)? {
306            Ok(Some(self.stack.pop()))
307        } else {
308            Ok(None)
309        }
310    }
311
312    fn mem_addr(&mut self, mem: &ast::Mem) -> usize {
313        let addr = self.stack.pop::<i32>() as u32 as usize;
314        addr + mem.offset as usize
315    }
316
317    fn load<V: LittleEndian>(&mut self, mem: &ast::Mem, at: usize) -> Result<V> {
318        let addr = self.mem_addr(mem);
319        self.module.memory.load(addr, at)
320    }
321
322    fn store<V: LittleEndian>(&mut self, mem: &ast::Mem, v: V, at: usize) -> Result<()> {
323        let addr = self.mem_addr(mem);
324        self.module.memory.store(addr, v, at)?;
325        Ok(())
326    }
327
328    // https://webassembly.github.io/spec/core/exec/instructions.html#exec-unop
329    fn unop<T, F>(&mut self, op: F)
330    where
331        T: StackAccess + LittleEndian,
332        F: FnOnce(T) -> T,
333    {
334        // Instead of popping value and pushing the result, directly modify stack top for optimization
335        let ret = op(self.stack.top());
336        self.stack.write_top_bytes(ret);
337    }
338
339    // https://webassembly.github.io/spec/core/exec/instructions.html#exec-binop
340    fn binop<T, F>(&mut self, op: F)
341    where
342        T: StackAccess + LittleEndian,
343        F: FnOnce(T, T) -> T,
344    {
345        // Instead of popping value and pushing the result, directly modify stack top for optimization
346        let c2 = self.stack.pop();
347        let c1 = self.stack.top();
348        let ret = op(c1, c2);
349        self.stack.write_top_bytes(ret);
350    }
351
352    fn binop_trap<T, F>(&mut self, op: F) -> Result<()>
353    where
354        T: StackAccess + LittleEndian,
355        F: FnOnce(T, T) -> Result<T>,
356    {
357        // Instead of popping value and pushing the result, directly modify stack top for optimization
358        let c2 = self.stack.pop();
359        let c1 = self.stack.top();
360        let ret = op(c1, c2)?;
361        self.stack.write_top_bytes(ret);
362        Ok(())
363    }
364
365    // https://webassembly.github.io/spec/core/exec/instructions.html#exec-testop
366    fn testop<T, F>(&mut self, op: F)
367    where
368        T: StackAccess + LittleEndian,
369        F: FnOnce(T) -> bool,
370    {
371        let ret = op(self.stack.top());
372        self.stack.write_top::<T, i32>(if ret { 1 } else { 0 });
373    }
374
375    // https://webassembly.github.io/spec/core/exec/instructions.html#exec-relop
376    fn relop<T, F>(&mut self, op: F)
377    where
378        T: StackAccess + LittleEndian,
379        F: FnOnce(T, T) -> bool,
380    {
381        let c2 = self.stack.pop();
382        let c1 = self.stack.top();
383        let ret = op(c1, c2);
384        self.stack.write_top::<T, i32>(if ret { 1i32 } else { 0 });
385    }
386
387    // https://webassembly.github.io/spec/core/exec/instructions.html#exec-cvtop
388    fn cvtop<T, U, F>(&mut self, op: F)
389    where
390        T: StackAccess,
391        U: StackAccess + LittleEndian + AsValType,
392        F: FnOnce(T) -> U,
393    {
394        let ret = op(self.stack.top());
395        self.stack.write_top::<T, U>(ret);
396    }
397
398    fn cvtop_trap<T, U, F>(&mut self, op: F) -> Result<()>
399    where
400        T: StackAccess,
401        U: StackAccess + LittleEndian + AsValType,
402        F: FnOnce(T) -> Result<U>,
403    {
404        let ret = op(self.stack.top())?;
405        self.stack.write_top::<T, U>(ret);
406        Ok(())
407    }
408}
409
410trait Execute<'m, 's, I: Importer> {
411    fn execute(&self, runtime: &mut Runtime<'m, 's, I>) -> ExecResult;
412}
413
414// https://webassembly.github.io/spec/core/exec/instructions.html#blocks
415impl<'m, 's, I: Importer> Execute<'m, 's, I> for Vec<ast::Instruction> {
416    fn execute(&self, runtime: &mut Runtime<'m, 's, I>) -> ExecResult {
417        // Run instruction sequence as block
418        for insn in self {
419            match insn.execute(runtime)? {
420                ExecState::Continue => {}
421                state => return Ok(state), // Stop executing this block on return or break
422            }
423        }
424        Ok(ExecState::Continue)
425    }
426}
427
428// https://webassembly.github.io/spec/core/exec/instructions.html
429impl<'m, 's, I: Importer> Execute<'m, 's, I> for ast::Instruction {
430    #[allow(clippy::cognitive_complexity)]
431    fn execute(&self, runtime: &mut Runtime<'m, 's, I>) -> ExecResult {
432        use ast::InsnKind::*;
433        use std::ops::*;
434        #[allow(clippy::float_cmp)]
435        match &self.kind {
436            // Control instructions
437            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-block
438            Block { ty, body } => {
439                let label = runtime.stack.push_label();
440                match body.execute(runtime)? {
441                    ExecState::Continue => {}
442                    ExecState::Ret => return Ok(ExecState::Ret),
443                    ExecState::Breaking(0) => runtime.stack.pop_label(&label, ty.is_some()),
444                    ExecState::Breaking(level) => return Ok(ExecState::Breaking(level - 1)),
445                }
446            }
447            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-loop
448            Loop { body, .. } => {
449                let label = runtime.stack.push_label();
450                loop {
451                    // Note: Difference between block and loop is the position on breaking. When reaching
452                    // to the end of instruction sequence, loop instruction ends execution of subsequence.
453                    match body.execute(runtime)? {
454                        ExecState::Continue => break,
455                        ExecState::Ret => return Ok(ExecState::Ret),
456                        ExecState::Breaking(0) => runtime.stack.pop_label(&label, false),
457                        ExecState::Breaking(level) => return Ok(ExecState::Breaking(level - 1)),
458                    }
459                }
460            }
461            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-if
462            If {
463                ty,
464                then_body,
465                else_body,
466            } => {
467                let cond: i32 = runtime.stack.pop();
468                let label = runtime.stack.push_label();
469                let insns = if cond != 0 { then_body } else { else_body };
470                match insns.execute(runtime)? {
471                    ExecState::Continue => {}
472                    ExecState::Ret => return Ok(ExecState::Ret),
473                    ExecState::Breaking(0) => runtime.stack.pop_label(&label, ty.is_some()),
474                    ExecState::Breaking(level) => return Ok(ExecState::Breaking(level - 1)),
475                }
476            }
477            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-unreachable
478            Unreachable => return Err(Trap::new(TrapReason::ReachUnreachable, self.start)),
479            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-nop
480            Nop => { /* yay! nothing to do */ }
481            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-br
482            Br(labelidx) => return Ok(ExecState::Breaking(*labelidx)),
483            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-br-if
484            BrIf(labelidx) => {
485                let cond: i32 = runtime.stack.pop();
486                if cond != 0 {
487                    return Ok(ExecState::Breaking(*labelidx));
488                }
489            }
490            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-br-table
491            BrTable { labels, default_label } => {
492                let idx = runtime.stack.pop::<i32>() as u32;
493                let idx = idx as usize;
494                let labelidx = if idx < labels.len() {
495                    labels[idx]
496                } else {
497                    *default_label
498                };
499                return Ok(ExecState::Breaking(labelidx));
500            }
501            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-return
502            Return => return Ok(ExecState::Ret),
503            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-call
504            Call(funcidx) => {
505                runtime.invoke_by_funcidx(*funcidx)?;
506            }
507            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-call-indirect
508            CallIndirect(typeidx) => {
509                let expected = &runtime.module.ast.types[*typeidx as usize];
510                let elemidx = runtime.stack.pop::<i32>() as u32;
511                let funcidx = runtime.module.table.at(elemidx as usize, self.start)?;
512                let func = &runtime.module.ast.funcs[funcidx as usize];
513                let actual = &runtime.module.ast.types[func.idx as usize];
514                if expected.params != actual.params || expected.results != actual.results {
515                    return Err(Trap::new(
516                        TrapReason::FuncSignatureMismatch {
517                            import: None,
518                            expected_params: expected.params.clone(),
519                            expected_results: expected.results.clone(),
520                            actual_params: actual.params.clone(),
521                            actual_results: actual.results.clone(),
522                        },
523                        self.start,
524                    ));
525                }
526                runtime.invoke_by_funcidx(funcidx)?;
527            }
528            // Parametric instructions
529            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-drop
530            Drop => {
531                runtime.stack.pop::<Value>();
532            }
533            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-select
534            Select => {
535                let cond: i32 = runtime.stack.pop();
536                let val2: Value = runtime.stack.pop();
537                // if cond != 0:
538                //     pop val2 -> pop val1 -> push val1 (skip pop/push val1)
539                // else:
540                //     pop val2 -> pop val1 -> push val2
541                if cond == 0 {
542                    let _val1: Value = runtime.stack.pop();
543                    runtime.stack.push(val2);
544                }
545            }
546            // Variable instructions
547            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-local-get
548            LocalGet(localidx) => {
549                runtime.stack.read_local(*localidx);
550            }
551            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-local-set
552            LocalSet(localidx) => {
553                runtime.stack.write_local(*localidx);
554                let _val: Value = runtime.stack.pop();
555            }
556            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-local-tee
557            LocalTee(localidx) => {
558                // Like local.set, but it does not change stack
559                runtime.stack.write_local(*localidx);
560            }
561            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-global-get
562            GlobalGet(globalidx) => match runtime.module.ast.globals[*globalidx as usize].ty {
563                ast::ValType::I32 => runtime.stack.push(runtime.module.globals.get::<i32>(*globalidx)),
564                ast::ValType::I64 => runtime.stack.push(runtime.module.globals.get::<i64>(*globalidx)),
565                ast::ValType::F32 => runtime.stack.push(runtime.module.globals.get::<f32>(*globalidx)),
566                ast::ValType::F64 => runtime.stack.push(runtime.module.globals.get::<f64>(*globalidx)),
567            },
568            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-global-set
569            GlobalSet(globalidx) => runtime.module.globals.set_any(*globalidx, runtime.stack.top()),
570            // Memory instructions
571            // https://webassembly.github.io/spec/core/exec/instructions.html#and
572            I32Load(mem) => {
573                let v: i32 = runtime.load(mem, self.start)?;
574                runtime.stack.push(v);
575            }
576            I64Load(mem) => {
577                let v: i64 = runtime.load(mem, self.start)?;
578                runtime.stack.push(v);
579            }
580            F32Load(mem) => {
581                let v: f32 = runtime.load(mem, self.start)?;
582                runtime.stack.push(v);
583            }
584            F64Load(mem) => {
585                let v: f64 = runtime.load(mem, self.start)?;
586                runtime.stack.push(v);
587            }
588            I32Load8S(mem) => {
589                let v: i8 = runtime.load(mem, self.start)?;
590                runtime.stack.push(v as i32);
591            }
592            I32Load8U(mem) => {
593                let v: u8 = runtime.load(mem, self.start)?;
594                runtime.stack.push(v as i32);
595            }
596            I32Load16S(mem) => {
597                let v: i16 = runtime.load(mem, self.start)?;
598                runtime.stack.push(v as i32);
599            }
600            I32Load16U(mem) => {
601                let v: u16 = runtime.load(mem, self.start)?;
602                runtime.stack.push(v as i32);
603            }
604            I64Load8S(mem) => {
605                let v: i8 = runtime.load(mem, self.start)?;
606                runtime.stack.push(v as i64);
607            }
608            I64Load8U(mem) => {
609                let v: u8 = runtime.load(mem, self.start)?;
610                runtime.stack.push(v as i64);
611            }
612            I64Load16S(mem) => {
613                let v: i16 = runtime.load(mem, self.start)?;
614                runtime.stack.push(v as i64);
615            }
616            I64Load16U(mem) => {
617                let v: u16 = runtime.load(mem, self.start)?;
618                runtime.stack.push(v as i64);
619            }
620            I64Load32S(mem) => {
621                let v: i32 = runtime.load(mem, self.start)?;
622                runtime.stack.push(v as i64);
623            }
624            I64Load32U(mem) => {
625                let v: u32 = runtime.load(mem, self.start)?;
626                runtime.stack.push(v as i64);
627            }
628            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-storen
629            I32Store(mem) => {
630                let v: i32 = runtime.stack.pop();
631                runtime.store(mem, v, self.start)?;
632            }
633            I64Store(mem) => {
634                let v: i64 = runtime.stack.pop();
635                runtime.store(mem, v, self.start)?;
636            }
637            F32Store(mem) => {
638                let v: f32 = runtime.stack.pop();
639                runtime.store(mem, v, self.start)?;
640            }
641            F64Store(mem) => {
642                let v: f64 = runtime.stack.pop();
643                runtime.store(mem, v, self.start)?;
644            }
645            I32Store8(mem) => {
646                let v: i32 = runtime.stack.pop();
647                runtime.store(mem, v as i8, self.start)?;
648            }
649            I32Store16(mem) => {
650                let v: i32 = runtime.stack.pop();
651                runtime.store(mem, v as i16, self.start)?;
652            }
653            I64Store8(mem) => {
654                let v: i64 = runtime.stack.pop();
655                runtime.store(mem, v as i8, self.start)?;
656            }
657            I64Store16(mem) => {
658                let v: i64 = runtime.stack.pop();
659                runtime.store(mem, v as i16, self.start)?;
660            }
661            I64Store32(mem) => {
662                let v: i64 = runtime.stack.pop();
663                runtime.store(mem, v as i32, self.start)?;
664            }
665            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-memory-size
666            MemorySize => runtime.stack.push(runtime.module.memory.size() as i32),
667            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-memory-grow
668            MemoryGrow => {
669                let pages: i32 = runtime.stack.pop();
670                let prev_pages = runtime.module.memory.grow(pages as u32);
671                runtime.stack.push(prev_pages);
672            }
673            // Numeric instructions
674            // https://webassembly.github.io/spec/core/exec/instructions.html#exec-const
675            I32Const(i) => runtime.stack.push(*i),
676            I64Const(i) => runtime.stack.push(*i),
677            F32Const(f) => runtime.stack.push(*f),
678            F64Const(f) => runtime.stack.push(*f),
679            // Integer operations
680            // https://webassembly.github.io/spec/core/exec/numerics.html#op-iclz
681            I32Clz => runtime.unop(|v: i32| v.leading_zeros() as i32),
682            I64Clz => runtime.unop(|v: i64| v.leading_zeros() as i64),
683            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ictz
684            I32Ctz => runtime.unop(|v: i32| v.trailing_zeros() as i32),
685            I64Ctz => runtime.unop(|v: i64| v.trailing_zeros() as i64),
686            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ipopcnt
687            I32Popcnt => runtime.unop(|v: i32| v.count_ones() as i32),
688            I64Popcnt => runtime.unop(|v: i64| v.count_ones() as i64),
689            // https://webassembly.github.io/spec/core/exec/numerics.html#op-iadd
690            I32Add => runtime.binop(i32::wrapping_add),
691            I64Add => runtime.binop(i64::wrapping_add),
692            // https://webassembly.github.io/spec/core/exec/numerics.html#op-isub
693            I32Sub => runtime.binop(i32::wrapping_sub),
694            I64Sub => runtime.binop(i64::wrapping_sub),
695            // https://webassembly.github.io/spec/core/exec/numerics.html#op-imul
696            I32Mul => runtime.binop(i32::wrapping_mul),
697            I64Mul => runtime.binop(i64::wrapping_mul),
698            // https://webassembly.github.io/spec/core/exec/numerics.html#op-idiv-s
699            // Note: According to i32.wast and i64.wast, integer overflow on idiv_s should be trapped.
700            // This is intended behavior: https://github.com/WebAssembly/spec/issues/1185#issuecomment-619412936
701            I32DivS => runtime.binop_trap(|l: i32, r| match l.checked_div(r) {
702                Some(i) => Ok(i),
703                None => Err(Trap::new(TrapReason::DivByZeroOrOverflow, self.start)),
704            })?,
705            I64DivS => runtime.binop_trap(|l: i64, r| match l.checked_div(r) {
706                Some(i) => Ok(i),
707                None => Err(Trap::new(TrapReason::DivByZeroOrOverflow, self.start)),
708            })?,
709            // https://webassembly.github.io/spec/core/exec/numerics.html#op-idiv-u
710            I32DivU => runtime.binop_trap(|l: i32, r| match (l as u32).checked_div(r as u32) {
711                Some(u) => Ok(u as i32),
712                None => Err(Trap::new(TrapReason::DivByZeroOrOverflow, self.start)),
713            })?,
714            I64DivU => runtime.binop_trap(|l: i64, r| match (l as u64).checked_div(r as u64) {
715                Some(u) => Ok(u as i64),
716                None => Err(Trap::new(TrapReason::DivByZeroOrOverflow, self.start)),
717            })?,
718            // https://webassembly.github.io/spec/core/exec/numerics.html#op-irem-s
719            // Note: rem_s should not cause overflow. For example, 0x80000000 % -1 causes overflow
720            // in Rust, but Wasm test case says it should return 0. Note that Go has special rule
721            // that x % -1 is 0 when x is the most negative value.
722            // This is intended behavior: https://github.com/WebAssembly/spec/issues/1185#issuecomment-619412936
723            I32RemS => runtime.binop_trap(|l: i32, r| {
724                if r == 0 {
725                    Err(Trap::new(TrapReason::RemZeroDivisor, self.start))
726                } else {
727                    Ok(l.wrapping_rem(r))
728                }
729            })?,
730            I64RemS => runtime.binop_trap(|l: i64, r| {
731                if r == 0 {
732                    Err(Trap::new(TrapReason::RemZeroDivisor, self.start))
733                } else {
734                    Ok(l.wrapping_rem(r))
735                }
736            })?,
737            // https://webassembly.github.io/spec/core/exec/numerics.html#op-irem-u
738            I32RemU => runtime.binop_trap(|l: i32, r| {
739                if r == 0 {
740                    Err(Trap::new(TrapReason::RemZeroDivisor, self.start))
741                } else {
742                    Ok((l as u32 % r as u32) as i32) // for unsigned integers overflow never occurs
743                }
744            })?,
745            I64RemU => runtime.binop_trap(|l: i64, r| {
746                if r == 0 {
747                    Err(Trap::new(TrapReason::RemZeroDivisor, self.start))
748                } else {
749                    Ok((l as u64 % r as u64) as i64) // for unsigned integers overflow never occurs
750                }
751            })?,
752            // https://webassembly.github.io/spec/core/exec/numerics.html#op-iand
753            I32And => runtime.binop(i32::bitand),
754            I64And => runtime.binop(i64::bitand),
755            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ior
756            I32Or => runtime.binop(i32::bitor),
757            I64Or => runtime.binop(i64::bitor),
758            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ixor
759            I32Xor => runtime.binop(i32::bitxor),
760            I64Xor => runtime.binop(i64::bitxor),
761            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ishl
762            I32Shl => runtime.binop(|l: i32, r| l.wrapping_shl(r as u32)),
763            I64Shl => runtime.binop(|l: i64, r| l.wrapping_shl(r as u32)),
764            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ishr-s
765            I32ShrS => runtime.binop(|l: i32, r| l.wrapping_shr(r as u32)),
766            I64ShrS => runtime.binop(|l: i64, r| l.wrapping_shr(r as u32)),
767            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ishr-u
768            I32ShrU => runtime.binop(|l: i32, r| (l as u32).wrapping_shr(r as u32) as i32),
769            I64ShrU => runtime.binop(|l: i64, r| (l as u64).wrapping_shr(r as u32) as i64),
770            // https://webassembly.github.io/spec/core/exec/numerics.html#op-irotl
771            I32Rotl => runtime.binop(|l: i32, r| l.rotate_left(r as u32)),
772            I64Rotl => runtime.binop(|l: i64, r| l.rotate_left(r as u32)),
773            // https://webassembly.github.io/spec/core/exec/numerics.html#op-irotr
774            I32Rotr => runtime.binop(|l: i32, r| l.rotate_right(r as u32)),
775            I64Rotr => runtime.binop(|l: i64, r| l.rotate_right(r as u32)),
776            // Float number operations
777            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fabs
778            F32Abs => runtime.unop(f32::abs),
779            F64Abs => runtime.unop(f64::abs),
780            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fneg
781            F32Neg => runtime.unop(f32::neg),
782            F64Neg => runtime.unop(f64::neg),
783            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fceil
784            F32Ceil => runtime.unop(f32::ceil),
785            F64Ceil => runtime.unop(f64::ceil),
786            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ffloor
787            F32Floor => runtime.unop(f32::floor),
788            F64Floor => runtime.unop(f64::floor),
789            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ftrunc
790            F32Trunc => runtime.unop(f32::trunc),
791            F64Trunc => runtime.unop(f64::trunc),
792            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest
793            F32Nearest => runtime.unop(|f: f32| {
794                // f32::round() is not available because behavior when two values are equally near
795                // is different. For example, 4.5f32.round() is 5.0 but (f32.nearest (f32.const 4.5))
796                // is 4.0.
797                let fround = f.round();
798                if (f - fround).abs() == 0.5 && fround % 2.0 != 0.0 {
799                    f.trunc()
800                } else {
801                    fround
802                }
803            }),
804            F64Nearest => runtime.unop(|f: f64| {
805                // f64::round() is not available for the same reason as f32.nearest
806                let fround = f.round();
807                if (f - fround).abs() == 0.5 && fround % 2.0 != 0.0 {
808                    f.trunc()
809                } else {
810                    fround
811                }
812            }),
813            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fsqrt
814            F32Sqrt => runtime.unop(f32::sqrt),
815            F64Sqrt => runtime.unop(f64::sqrt),
816            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fadd
817            F32Add => runtime.binop(f32::add),
818            F64Add => runtime.binop(f64::add),
819            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fsub
820            F32Sub => runtime.binop(f32::sub),
821            F64Sub => runtime.binop(f64::sub),
822            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmul
823            F32Mul => runtime.binop(f32::mul),
824            F64Mul => runtime.binop(f64::mul),
825            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fdiv
826            F32Div => runtime.binop(f32::div),
827            F64Div => runtime.binop(f64::div),
828            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmin
829            F32Min => runtime.binop(fmin::<f32>),
830            F64Min => runtime.binop(fmin::<f64>),
831            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmax
832            F32Max => runtime.binop(fmax::<f32>),
833            F64Max => runtime.binop(fmax::<f64>),
834            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fcopysign
835            F32Copysign => runtime.binop(f32::copysign),
836            F64Copysign => runtime.binop(f64::copysign),
837            // Integer comparison
838            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ieqz
839            I32Eqz => runtime.testop(|i: i32| i == 0),
840            I64Eqz => runtime.testop(|i: i64| i == 0),
841            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ieq
842            I32Eq => runtime.relop(|l: i32, r| l == r),
843            I64Eq => runtime.relop(|l: i64, r| l == r),
844            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ine
845            I32Ne => runtime.relop(|l: i32, r| l != r),
846            I64Ne => runtime.relop(|l: i64, r| l != r),
847            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ilt-s
848            I32LtS => runtime.relop(|l: i32, r| l < r),
849            I64LtS => runtime.relop(|l: i64, r| l < r),
850            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ilt-u
851            I32LtU => runtime.relop(|l: i32, r| (l as u32) < r as u32),
852            I64LtU => runtime.relop(|l: i64, r| (l as u64) < r as u64),
853            // https://webassembly.github.io/spec/core/exec/numerics.html#op-igt-s
854            I32GtS => runtime.relop(|l: i32, r| l > r),
855            I64GtS => runtime.relop(|l: i64, r| l > r),
856            // https://webassembly.github.io/spec/core/exec/numerics.html#op-igt-u
857            I32GtU => runtime.relop(|l: i32, r| l as u32 > r as u32),
858            I64GtU => runtime.relop(|l: i64, r| l as u64 > r as u64),
859            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ile-s
860            I32LeS => runtime.relop(|l: i32, r| l <= r),
861            I64LeS => runtime.relop(|l: i64, r| l <= r),
862            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ile-u
863            I32LeU => runtime.relop(|l: i32, r| l as u32 <= r as u32),
864            I64LeU => runtime.relop(|l: i64, r| l as u64 <= r as u64),
865            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ige-s
866            I32GeS => runtime.relop(|l: i32, r| l >= r),
867            I64GeS => runtime.relop(|l: i64, r| l >= r),
868            // https://webassembly.github.io/spec/core/exec/numerics.html#op-ige-u
869            I32GeU => runtime.relop(|l: i32, r| l as u32 >= r as u32),
870            I64GeU => runtime.relop(|l: i64, r| l as u64 >= r as u64),
871            // Float number comparison
872            // https://webassembly.github.io/spec/core/exec/numerics.html#op-feq
873            F32Eq => runtime.relop(|l: f32, r| l == r),
874            F64Eq => runtime.relop(|l: f64, r| l == r),
875            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fne
876            F32Ne => runtime.relop(|l: f32, r| l != r),
877            F64Ne => runtime.relop(|l: f64, r| l != r),
878            // https://webassembly.github.io/spec/core/exec/numerics.html#op-flt
879            F32Lt => runtime.relop(|l: f32, r| l < r),
880            F64Lt => runtime.relop(|l: f64, r| l < r),
881            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fgt
882            F32Gt => runtime.relop(|l: f32, r| l > r),
883            F64Gt => runtime.relop(|l: f64, r| l > r),
884            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fle
885            F32Le => runtime.relop(|l: f32, r| l <= r),
886            F64Le => runtime.relop(|l: f64, r| l <= r),
887            // https://webassembly.github.io/spec/core/exec/numerics.html#op-fge
888            F32Ge => runtime.relop(|l: f32, r| l >= r),
889            F64Ge => runtime.relop(|l: f64, r| l >= r),
890            // Conversion
891            // https://webassembly.github.io/spec/core/exec/numerics.html#op-extend-u
892            I64ExtendI32U => runtime.cvtop(|v: i32| v as u32 as i64),
893            // https://webassembly.github.io/spec/core/exec/numerics.html#op-extend-s
894            I64ExtendI32S => runtime.cvtop(|v: i32| v as i64),
895            // https://webassembly.github.io/spec/core/exec/numerics.html#op-wrap
896            I32WrapI64 => runtime.cvtop(|v: i64| v as i32),
897            // https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-u
898            I32TruncF32U => runtime.cvtop_trap(|v: f32| match cast::f32_to_u32(v) {
899                Some(u) => Ok(u as i32),
900                None => Err(Trap::out_of_range(v, "u32", self.start)),
901            })?,
902            I32TruncF64U => runtime.cvtop_trap(|v: f64| match cast::f64_to_u32(v) {
903                Some(u) => Ok(u as i32),
904                None => Err(Trap::out_of_range(v, "u32", self.start)),
905            })?,
906            I64TruncF32U => runtime.cvtop_trap(|v: f32| match cast::f32_to_u64(v) {
907                Some(u) => Ok(u as i64),
908                None => Err(Trap::out_of_range(v, "u64", self.start)),
909            })?,
910            I64TruncF64U => runtime.cvtop_trap(|v: f64| match cast::f64_to_u64(v) {
911                Some(u) => Ok(u as i64),
912                None => Err(Trap::out_of_range(v, "u64", self.start)),
913            })?,
914            // https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s
915            I32TruncF32S => runtime.cvtop_trap(|v: f32| match cast::f32_to_i32(v) {
916                Some(u) => Ok(u),
917                None => Err(Trap::out_of_range(v, "i32", self.start)),
918            })?,
919            I32TruncF64S => runtime.cvtop_trap(|v: f64| match cast::f64_to_i32(v) {
920                Some(u) => Ok(u),
921                None => Err(Trap::out_of_range(v, "i32", self.start)),
922            })?,
923            I64TruncF32S => runtime.cvtop_trap(|v: f32| match cast::f32_to_i64(v) {
924                Some(u) => Ok(u),
925                None => Err(Trap::out_of_range(v, "i64", self.start)),
926            })?,
927            I64TruncF64S => runtime.cvtop_trap(|v: f64| match cast::f64_to_i64(v) {
928                Some(u) => Ok(u),
929                None => Err(Trap::out_of_range(v, "i64", self.start)),
930            })?,
931            // https://webassembly.github.io/spec/core/exec/numerics.html#op-promote
932            F64PromoteF32 => runtime.cvtop(|v: f32| v as f64),
933            // https://webassembly.github.io/spec/core/exec/numerics.html#op-demote
934            F32DemoteF64 => runtime.cvtop(|v: f64| v as f32),
935            // https://webassembly.github.io/spec/core/exec/numerics.html#op-convert-u
936            F32ConvertI32U => runtime.cvtop(|v: i32| v as u32 as f32),
937            F32ConvertI64U => runtime.cvtop(|v: i64| v as u64 as f32),
938            F64ConvertI32U => runtime.cvtop(|v: i32| v as u32 as f64),
939            F64ConvertI64U => runtime.cvtop(|v: i64| v as u64 as f64),
940            // https://webassembly.github.io/spec/core/exec/numerics.html#op-convert-s
941            F32ConvertI32S => runtime.cvtop(|v: i32| v as f32),
942            F32ConvertI64S => runtime.cvtop(|v: i64| v as f32),
943            F64ConvertI32S => runtime.cvtop(|v: i32| v as f64),
944            F64ConvertI64S => runtime.cvtop(|v: i64| v as f64),
945            // https://webassembly.github.io/spec/core/exec/numerics.html#op-reinterpret
946            // Don't need to modify stack. Just changing type to t2 is enough.
947            I32ReinterpretF32 => runtime.stack.write_top_type(i32::VAL_TYPE),
948            I64ReinterpretF64 => runtime.stack.write_top_type(i64::VAL_TYPE),
949            F32ReinterpretI32 => runtime.stack.write_top_type(f32::VAL_TYPE),
950            F64ReinterpretI64 => runtime.stack.write_top_type(f64::VAL_TYPE),
951            // https://webassembly.github.io/spec/core/exec/numerics.html#op-iextendn-s
952            I32Extend8S => runtime.cvtop(|v: i32| i32::from(v as i8)),
953            I32Extend16S => runtime.cvtop(|v: i32| i32::from(v as i16)),
954            I64Extend8S => runtime.cvtop(|v: i64| i64::from(v as i8)),
955            I64Extend16S => runtime.cvtop(|v: i64| i64::from(v as i16)),
956            I64Extend32S => runtime.cvtop(|v: i64| i64::from(v as i32)),
957        }
958        Ok(ExecState::Continue)
959    }
960}
961
962#[cfg(test)]
963mod tests {
964    use super::*;
965    use crate::import::DefaultImporter;
966    use std::borrow::Cow;
967    use std::env;
968    use std::fmt;
969    use std::fs;
970    use std::io::{self, Read, Write};
971    use std::path::PathBuf;
972    use std::result;
973    use wain_syntax_text::parse;
974    use wain_validate::validate;
975
976    struct Discard;
977
978    impl Read for Discard {
979        fn read(&mut self, b: &mut [u8]) -> io::Result<usize> {
980            Ok(b.len())
981        }
982    }
983
984    impl Write for Discard {
985        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
986            Ok(buf.len())
987        }
988        fn flush(&mut self) -> io::Result<()> {
989            Ok(())
990        }
991    }
992
993    #[test]
994    fn hello_world() {
995        fn unwrap<T, E: fmt::Display>(res: result::Result<T, E>) -> T {
996            match res {
997                Ok(x) => x,
998                Err(e) => panic!("unwrap failed with error message:\n{}", e),
999            }
1000        }
1001
1002        fn exec(file: PathBuf) -> Result<Vec<u8>> {
1003            let source = fs::read_to_string(file).unwrap();
1004            let ast = unwrap(parse(&source));
1005            unwrap(validate(&ast));
1006            let mut stdout = vec![];
1007            {
1008                let importer = DefaultImporter::with_stdio(Discard, &mut stdout);
1009                let mut runtime = unwrap(Runtime::instantiate(&ast.module, importer));
1010                runtime.invoke("_start", &[])?;
1011            }
1012            Ok(stdout)
1013        }
1014
1015        let mut dir = env::current_dir().unwrap();
1016        dir.pop();
1017        dir.push("examples");
1018        dir.push("hello");
1019        let dir = dir;
1020
1021        let stdout = exec(dir.join("hello.wat")).unwrap();
1022        assert_eq!(stdout, b"Hello, world\n");
1023
1024        let stdout = exec(dir.join("hello_global.wat")).unwrap();
1025        assert_eq!(stdout, b"Hello, world\n");
1026
1027        let stdout = exec(dir.join("hello_indirect_call.wat")).unwrap();
1028        assert_eq!(stdout, b"Hello, world\n");
1029
1030        let stdout = exec(dir.join("hello_struct.wat")).unwrap();
1031        assert_eq!(stdout, b"Hello, world\n");
1032    }
1033
1034    fn exec_insns(ty: ast::ValType, insns: Vec<ast::InsnKind>) -> Result<Option<Value>> {
1035        let expr = insns
1036            .into_iter()
1037            .map(|kind| ast::Instruction { start: 0, kind })
1038            .collect();
1039
1040        let mut module = ast::Module::default();
1041        module.memories.push(ast::Memory {
1042            start: 0,
1043            ty: ast::MemType {
1044                limit: ast::Limits::From(0),
1045            },
1046            import: None,
1047        });
1048        module.types.push(ast::FuncType {
1049            start: 0,
1050            params: vec![],
1051            results: vec![ty],
1052        });
1053        module.funcs.push(ast::Func {
1054            start: 0,
1055            idx: 0,
1056            kind: ast::FuncKind::Body { locals: vec![], expr },
1057        });
1058        module.exports.push(ast::Export {
1059            start: 0,
1060            name: ast::Name(Cow::Borrowed("test")),
1061            kind: ast::ExportKind::Func(0),
1062        });
1063
1064        let importer = DefaultImporter::with_stdio(Discard, Discard);
1065        let mut runtime = Runtime::instantiate(&module, importer)?;
1066        runtime.invoke("test", &[])
1067    }
1068
1069    #[test]
1070    fn nearest_edge_cases() {
1071        use ast::InsnKind::*;
1072        use ast::ValType::*;
1073
1074        let f = exec_insns(F32, vec![F32Const(4.5), F32Nearest]).unwrap().unwrap();
1075        assert!(matches!(f, Value::F32(f) if f == 4.0));
1076
1077        let f = exec_insns(F32, vec![F32Const(3.5), F32Nearest]).unwrap().unwrap();
1078        assert!(matches!(f, Value::F32(f) if f == 4.0));
1079
1080        let f = exec_insns(F32, vec![F32Const(-0.5), F32Nearest]).unwrap().unwrap();
1081        assert!(matches!(f, Value::F32(f) if f == 0.0 && f.is_sign_negative())); // -0.0
1082
1083        let f = exec_insns(F32, vec![F32Const(0.5), F32Nearest]).unwrap().unwrap();
1084        assert!(matches!(f, Value::F32(f) if f == 0.0 && f.is_sign_positive())); // +0.0
1085
1086        let f = exec_insns(F64, vec![F64Const(4.5), F64Nearest]).unwrap().unwrap();
1087        assert!(matches!(f, Value::F64(f) if f == 4.0));
1088
1089        let f = exec_insns(F64, vec![F64Const(3.5), F64Nearest]).unwrap().unwrap();
1090        assert!(matches!(f, Value::F64(f) if f == 4.0));
1091
1092        let f = exec_insns(F64, vec![F64Const(-0.5), F64Nearest]).unwrap().unwrap();
1093        assert!(matches!(f, Value::F64(f) if f == 0.0 && f.is_sign_negative())); // -0.0
1094
1095        let f = exec_insns(F64, vec![F64Const(0.5), F64Nearest]).unwrap().unwrap();
1096        assert!(matches!(f, Value::F64(f) if f == 0.0 && f.is_sign_positive() /* +0.0 */));
1097    }
1098
1099    #[test]
1100    fn int_overflow() {
1101        use ast::InsnKind::*;
1102        use ast::ValType::*;
1103
1104        let i = exec_insns(I32, vec![I32Const(i32::MAX), I32Const(1), I32Add])
1105            .unwrap()
1106            .unwrap();
1107        assert!(matches!(i, Value::I32(i) if i == i32::MIN));
1108
1109        let i = exec_insns(I32, vec![I32Const(i32::MIN), I32Const(1), I32Sub])
1110            .unwrap()
1111            .unwrap();
1112        assert!(matches!(i, Value::I32(i) if i == i32::MAX));
1113
1114        let i = exec_insns(I32, vec![I32Const(i32::MIN), I32Const(-1), I32Mul])
1115            .unwrap()
1116            .unwrap();
1117        assert!(matches!(i, Value::I32(i) if i == i32::MIN));
1118
1119        let i = exec_insns(I64, vec![I64Const(i64::MAX), I64Const(1), I64Add])
1120            .unwrap()
1121            .unwrap();
1122        assert!(matches!(i, Value::I64(i) if i == i64::MIN));
1123
1124        let i = exec_insns(I64, vec![I64Const(i64::MIN), I64Const(1), I64Sub])
1125            .unwrap()
1126            .unwrap();
1127        assert!(matches!(i, Value::I64(i) if i == i64::MAX));
1128
1129        let i = exec_insns(I64, vec![I64Const(i64::MIN), I64Const(-1), I64Mul])
1130            .unwrap()
1131            .unwrap();
1132        assert!(matches!(i, Value::I64(i) if i == i64::MIN));
1133    }
1134
1135    #[test]
1136    fn div_rem_edge_cases() {
1137        use ast::InsnKind::*;
1138        use ast::ValType::*;
1139
1140        let i = exec_insns(I32, vec![I32Const(i32::MIN), I32Const(-1), I32RemS])
1141            .unwrap()
1142            .unwrap();
1143        assert!(matches!(i, Value::I32(0)), "{:?}", i);
1144
1145        let i = exec_insns(I64, vec![I64Const(i64::MIN), I64Const(-1), I64RemS])
1146            .unwrap()
1147            .unwrap();
1148        assert!(matches!(i, Value::I64(0)), "{:?}", i);
1149
1150        let e = exec_insns(I32, vec![I32Const(1), I32Const(0), I32RemS]).unwrap_err();
1151        assert!(matches!(e.reason, TrapReason::RemZeroDivisor));
1152        let e = exec_insns(I32, vec![I32Const(1), I32Const(0), I32RemU]).unwrap_err();
1153        assert!(matches!(e.reason, TrapReason::RemZeroDivisor));
1154        let e = exec_insns(I64, vec![I64Const(1), I64Const(0), I64RemS]).unwrap_err();
1155        assert!(matches!(e.reason, TrapReason::RemZeroDivisor));
1156        let e = exec_insns(I64, vec![I64Const(1), I64Const(0), I64RemU]).unwrap_err();
1157        assert!(matches!(e.reason, TrapReason::RemZeroDivisor));
1158    }
1159
1160    #[test]
1161    fn fmin_edge_cases() {
1162        use ast::InsnKind::*;
1163        use ast::ValType::*;
1164
1165        let i = exec_insns(F32, vec![F32Const(0.0), F32Const(-0.0), F32Min])
1166            .unwrap()
1167            .unwrap();
1168        assert!(matches!(i, Value::F32(f) if f.to_bits() == 0x8000_0000));
1169        let i = exec_insns(F32, vec![F32Const(-0.0), F32Const(0.0), F32Min])
1170            .unwrap()
1171            .unwrap();
1172        assert!(matches!(i, Value::F32(f) if f.to_bits() == 0x8000_0000));
1173        let i = exec_insns(F32, vec![F32Const(1.0), F32Const(1.0), F32Min])
1174            .unwrap()
1175            .unwrap();
1176        assert!(matches!(i, Value::F32(f) if f == 1.0));
1177        let i = exec_insns(F32, vec![F32Const(-42.0), F32Const(-42.0), F32Min])
1178            .unwrap()
1179            .unwrap();
1180        assert!(matches!(i, Value::F32(f) if f == -42.0));
1181        let i = exec_insns(
1182            F32,
1183            vec![
1184                F32Const(f32::NEG_INFINITY),
1185                F32Const(f32::from_bits(0x7f80_0001)),
1186                F32Min,
1187            ],
1188        )
1189        .unwrap()
1190        .unwrap();
1191        assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fc0_0001));
1192        let i = exec_insns(
1193            F32,
1194            vec![
1195                F32Const(f32::from_bits(0x7fff_ffff)),
1196                F32Const(f32::NEG_INFINITY),
1197                F32Min,
1198            ],
1199        )
1200        .unwrap()
1201        .unwrap();
1202        assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fff_ffff));
1203        let i = exec_insns(
1204            F32,
1205            vec![
1206                F32Const(f32::from_bits(0x7f80_0001)),
1207                F32Const(f32::from_bits(0x7fff_ffff)),
1208                F32Min,
1209            ],
1210        )
1211        .unwrap()
1212        .unwrap();
1213        assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fc0_0001));
1214
1215        let i = exec_insns(F64, vec![F64Const(0.0), F64Const(-0.0), F64Min])
1216            .unwrap()
1217            .unwrap();
1218        assert!(matches!(i, Value::F64(f) if f.to_bits() == 0x8000_0000_0000_0000));
1219        let i = exec_insns(F64, vec![F64Const(-0.0), F64Const(0.0), F64Min])
1220            .unwrap()
1221            .unwrap();
1222        assert!(matches!(i, Value::F64(f) if f.to_bits() == 0x8000_0000_0000_0000));
1223        let i = exec_insns(F64, vec![F64Const(1.0), F64Const(1.0), F64Min])
1224            .unwrap()
1225            .unwrap();
1226        assert!(matches!(i, Value::F64(f) if f == 1.0));
1227        let i = exec_insns(F64, vec![F64Const(-42.0), F64Const(-42.0), F64Min])
1228            .unwrap()
1229            .unwrap();
1230        assert!(matches!(i, Value::F64(f) if f == -42.0));
1231        let i = exec_insns(
1232            F64,
1233            vec![
1234                F64Const(f64::NEG_INFINITY),
1235                F64Const(f64::from_bits(0x7ff0_0000_0000_0001)),
1236                F64Min,
1237            ],
1238        )
1239        .unwrap()
1240        .unwrap();
1241        assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7ff8_0000_0000_0001));
1242        let i = exec_insns(
1243            F64,
1244            vec![
1245                F64Const(f64::from_bits(0x7fff_ffff_ffff_ffff)),
1246                F64Const(f64::NEG_INFINITY),
1247                F64Min,
1248            ],
1249        )
1250        .unwrap()
1251        .unwrap();
1252        assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7fff_ffff_ffff_ffff));
1253        let i = exec_insns(
1254            F64,
1255            vec![
1256                F64Const(f64::from_bits(0x7ff0_0000_0000_0001)),
1257                F64Const(f64::from_bits(0x7fff_ffff_ffff_ffff)),
1258                F64Min,
1259            ],
1260        )
1261        .unwrap()
1262        .unwrap();
1263        assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7ff8_0000_0000_0001));
1264    }
1265
1266    #[test]
1267    fn fmax_edge_cases() {
1268        use ast::InsnKind::*;
1269        use ast::ValType::*;
1270
1271        let i = exec_insns(F32, vec![F32Const(0.0), F32Const(-0.0), F32Max])
1272            .unwrap()
1273            .unwrap();
1274        assert!(matches!(i, Value::F32(f) if f.to_bits() == 0x0000_0000));
1275        let i = exec_insns(F32, vec![F32Const(-0.0), F32Const(0.0), F32Max])
1276            .unwrap()
1277            .unwrap();
1278        assert!(matches!(i, Value::F32(f) if f.to_bits() == 0x0000_0000));
1279        let i = exec_insns(F32, vec![F32Const(1.0), F32Const(1.0), F32Max])
1280            .unwrap()
1281            .unwrap();
1282        assert!(matches!(i, Value::F32(f) if f == 1.0));
1283        let i = exec_insns(F32, vec![F32Const(-42.0), F32Const(-42.0), F32Max])
1284            .unwrap()
1285            .unwrap();
1286        assert!(matches!(i, Value::F32(f) if f == -42.0));
1287        let i = exec_insns(
1288            F32,
1289            vec![F32Const(f32::INFINITY), F32Const(f32::from_bits(0x7f80_0001)), F32Max],
1290        )
1291        .unwrap()
1292        .unwrap();
1293        assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fc0_0001));
1294        let i = exec_insns(
1295            F32,
1296            vec![F32Const(f32::from_bits(0x7fff_ffff)), F32Const(f32::INFINITY), F32Max],
1297        )
1298        .unwrap()
1299        .unwrap();
1300        assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fff_ffff));
1301        let i = exec_insns(
1302            F32,
1303            vec![
1304                F32Const(f32::from_bits(0x7f80_0001)),
1305                F32Const(f32::from_bits(0x7fff_ffff)),
1306                F32Max,
1307            ],
1308        )
1309        .unwrap()
1310        .unwrap();
1311        assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fc0_0001));
1312
1313        let i = exec_insns(F64, vec![F64Const(0.0), F64Const(-0.0), F64Max])
1314            .unwrap()
1315            .unwrap();
1316        assert!(matches!(i, Value::F64(f) if f.to_bits() == 0x0000_0000_0000_0000));
1317        let i = exec_insns(F64, vec![F64Const(-0.0), F64Const(0.0), F64Max])
1318            .unwrap()
1319            .unwrap();
1320        assert!(matches!(i, Value::F64(f) if f.to_bits() == 0x0000_0000_0000_0000));
1321        let i = exec_insns(F64, vec![F64Const(1.0), F64Const(1.0), F64Max])
1322            .unwrap()
1323            .unwrap();
1324        assert!(matches!(i, Value::F64(f) if f == 1.0));
1325        let i = exec_insns(F64, vec![F64Const(-42.0), F64Const(-42.0), F64Max])
1326            .unwrap()
1327            .unwrap();
1328        assert!(matches!(i, Value::F64(f) if f == -42.0));
1329        let i = exec_insns(
1330            F64,
1331            vec![
1332                F64Const(f64::INFINITY),
1333                F64Const(f64::from_bits(0x7ff0_0000_0000_0001)),
1334                F64Max,
1335            ],
1336        )
1337        .unwrap()
1338        .unwrap();
1339        assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7ff8_0000_0000_0001));
1340        let i = exec_insns(
1341            F64,
1342            vec![
1343                F64Const(f64::from_bits(0x7fff_ffff_ffff_ffff)),
1344                F64Const(f64::INFINITY),
1345                F64Max,
1346            ],
1347        )
1348        .unwrap()
1349        .unwrap();
1350        assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7fff_ffff_ffff_ffff));
1351        let i = exec_insns(
1352            F64,
1353            vec![
1354                F64Const(f64::from_bits(0x7ff0_0000_0000_0001)),
1355                F64Const(f64::from_bits(0x7fff_ffff_ffff_ffff)),
1356                F64Max,
1357            ],
1358        )
1359        .unwrap()
1360        .unwrap();
1361        assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7ff8_0000_0000_0001));
1362    }
1363}