//! A small WebAssembly interpreter for the wasm backend's tests: it decodes and runs the //! subset the backend emits (MVP numerics, sign-extension, saturating truncation, bulk memory, //! multi-value, exceptions with try_table, a SIMD128 subset), checks the section layout and //! traps on what it does not know. Test code only: no third-party runtime. #![allow(dead_code)] use std::collections::HashMap; #[derive(Debug, Clone, PartialEq)] pub enum Trap { Trap(String), Exception, } fn t(m: &str) -> Result { Err(Trap::Trap(m.to_string())) } #[derive(Clone, Debug, PartialEq)] pub struct FType { pub params: Vec, pub results: Vec, } pub type HostFn = Box, &[u64]) -> Result, Trap>>; pub struct Func { ty: u32, locals: Vec, code: std::rc::Rc>, /// block/loop/if/try_table start (after the opcode) -> (else pc or 0, end pc) ctl: std::rc::Rc>, } pub struct Instance { pub types: Vec, pub imports: Vec<(String, String, u32)>, pub funcs: Vec, pub table: Vec>, pub mem: Vec, /// memory64 (limits flag 0x04): addresses are i64 pub mem64: bool, pub globals: Vec, pub exports: HashMap, pub host: Vec>, pub steps: u64, pub max_steps: u64, depth: u32, } struct Rd<'a> { b: &'a [u8], p: usize, } impl<'a> Rd<'a> { fn u8(&mut self) -> Result { let v = *self.b.get(self.p).ok_or(Trap::Trap("eof".into()))?; self.p += 1; Ok(v) } fn u(&mut self) -> Result { let mut r = 0u64; let mut s = 0; loop { let b = self.u8()?; r |= ((b & 0x7f) as u64) << s; s += 7; if b & 0x80 == 0 { return Ok(r); } } } fn s(&mut self) -> Result { let mut r = 0i64; let mut s = 0; loop { let b = self.u8()?; r |= ((b & 0x7f) as i64) << s; s += 7; if b & 0x80 == 0 { if s < 64 && b & 0x40 != 0 { r |= -1i64 << s; } return Ok(r); } } } fn bytes(&mut self, n: usize) -> Result<&'a [u8], Trap> { let r = self.b.get(self.p..self.p + n).ok_or(Trap::Trap("eof".into()))?; self.p += n; Ok(r) } fn name(&mut self) -> Result { let n = self.u()? as usize; Ok(String::from_utf8_lossy(self.bytes(n)?).to_string()) } } /// Skips one instruction's immediates (after its opcode `op`). fn skip_imm(r: &mut Rd, op: u8) -> Result<(), Trap> { match op { 0x02 | 0x03 | 0x04 | 0x06 => { r.s()?; } 0x1F => { r.s()?; let n = r.u()?; for _ in 0..n { let k = r.u8()?; if k < 2 { r.u()?; } r.u()?; } } 0x08 | 0x0C | 0x0D | 0x10 | 0x20 | 0x21 | 0x22 | 0x23 | 0x24 => { r.u()?; } 0x0E => { let n = r.u()?; for _ in 0..=n { r.u()?; } } 0x11 => { r.u()?; r.u()?; } 0x28..=0x3E => { r.u()?; r.u()?; } 0x3F | 0x40 => { r.u8()?; } 0x41 | 0x42 => { r.s()?; } 0x43 => { r.bytes(4)?; } 0x44 => { r.bytes(8)?; } 0xFC => { let k = r.u()?; match k { 8 => { r.u()?; r.u8()?; } 9 => { r.u()?; } 10 => { r.u8()?; r.u8()?; } 11 => { r.u8()?; } _ => {} } } 0xFD => { let k = r.u()?; match k { 0 | 11 => { r.u()?; r.u()?; } 12 | 13 => { r.bytes(16)?; } 21..=34 => { r.u8()?; } _ => {} } } _ => {} } Ok(()) } fn ctl_map(code: &[u8], start: usize) -> Result, Trap> { let mut m = HashMap::new(); let mut st: Vec<(usize, usize)> = Vec::new(); let mut r = Rd { b: code, p: start }; while r.p < code.len() { let op = r.u8()?; let here = r.p; match op { 0x02 | 0x03 | 0x04 | 0x1F | 0x06 => { st.push((here, 0)); } 0x05 | 0x19 => { st.last_mut().ok_or(Trap::Trap("else".into()))?.1 = here; } 0x0B => { if let Some((s, e)) = st.pop() { m.insert(s, (e, here)); } } _ => {} } skip_imm(&mut r, op)?; } Ok(m) } impl Instance { pub fn new(bytes: &[u8]) -> Result { let mut r = Rd { b: bytes, p: 0 }; if r.bytes(8)? != [0, 0x61, 0x73, 0x6D, 1, 0, 0, 0] { return t("magic"); } let mut inst = Instance { types: vec![], imports: vec![], funcs: vec![], table: vec![], mem: vec![], mem64: false, globals: vec![], exports: HashMap::new(), host: vec![], steps: 0, max_steps: 50_000_000, depth: 0 }; let mut fty: Vec = vec![]; let mut last_id = 0u8; let mut datas: Vec<(Option, Vec)> = vec![]; let order = |id: u8| -> u8 { match id { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 13 => 6, 6 => 7, 7 => 8, 8 => 9, 9 => 10, 12 => 11, 10 => 12, 11 => 13, _ => 0, } }; while r.p < bytes.len() { let id = r.u8()?; let n = r.u()? as usize; let body = r.bytes(n)?; if id != 0 { if order(id) <= last_id { return t(&format!("section {} out of order", id)); } last_id = order(id); } let mut s = Rd { b: body, p: 0 }; match id { 0 => {} 1 => { for _ in 0..s.u()? { if s.u8()? != 0x60 { return t("functype"); } let np = s.u()? as usize; let params = s.bytes(np)?.to_vec(); let nr = s.u()? as usize; let results = s.bytes(nr)?.to_vec(); inst.types.push(FType { params, results }); } } 2 => { for _ in 0..s.u()? { let m = s.name()?; let f = s.name()?; let k = s.u8()?; let ti = s.u()? as u32; if k != 0 { return t("import kind"); } inst.imports.push((m, f, ti)); inst.host.push(None); } } 3 => { for _ in 0..s.u()? { fty.push(s.u()? as u32); } } 4 => { s.u()?; s.u8()?; s.u8()?; let n = s.u()? as usize; inst.table = vec![None; n]; } 5 => { s.u()?; let fl = s.u8()?; if fl & !0x04 != 0 { return t("memory limits flags"); } inst.mem64 = fl & 0x04 != 0; let pages = s.u()? as usize; inst.mem = vec![0; pages * 65536]; } 13 => {} 6 => { for _ in 0..s.u()? { s.u8()?; s.u8()?; let op = s.u8()?; let v = s.s()?; s.u8()?; inst.globals.push(if op == 0x41 { v as i32 as u32 as u64 } else { v as u64 }); } } 7 => { for _ in 0..s.u()? { let n = s.name()?; let k = s.u8()?; let i = s.u()? as u32; inst.exports.insert(n, (k, i)); } } 9 => { for _ in 0..s.u()? { if s.u()? != 0 { return t("elem flags"); } s.u8()?; let off = s.s()? as usize; s.u8()?; for k in 0..s.u()? as usize { let f = s.u()? as u32; inst.table[off + k] = Some(f); } } } 12 => {} 10 => { let n = s.u()? as usize; if n != fty.len() { return t("code count"); } for k in 0..n { let sz = s.u()? as usize; let fb = s.bytes(sz)?; let mut q = Rd { b: fb, p: 0 }; let mut locals = vec![]; for _ in 0..q.u()? { let c = q.u()?; let ty = q.u8()?; for _ in 0..c { locals.push(ty); } } let code = fb.to_vec(); let ctl = ctl_map(&code, q.p)?; let start = q.p; let mut ctl = ctl; ctl.insert(usize::MAX, (start, 0)); inst.funcs.push(Func { ty: fty[k], locals, code: std::rc::Rc::new(code), ctl: std::rc::Rc::new(ctl) }); } } 11 => { for _ in 0..s.u()? { let fl = s.u()?; let addr = if fl == 0 { let op = s.u8()?; let v = s.s()?; s.u8()?; Some(if op == 0x41 { v as i32 as u32 as u64 } else { v as u64 }) } else { None }; let n = s.u()? as usize; datas.push((addr, s.bytes(n)?.to_vec())); } } _ => return t(&format!("section {}", id)), } } for (a, d) in datas { if let Some(a) = a { let a = a as usize; if a + d.len() > inst.mem.len() { return t("data out of memory"); } inst.mem[a..a + d.len()].copy_from_slice(&d); } } Ok(inst) } pub fn bind(&mut self, m: &str, f: &str, h: HostFn) { for (k, (mm, ff, _)) in self.imports.iter().enumerate() { if mm == m && ff == f { self.host[k] = Some(h); return; } } } pub fn call_export(&mut self, name: &str, args: &[u64]) -> Result, Trap> { let (k, i) = *self.exports.get(name).ok_or(Trap::Trap(format!("no export {}", name)))?; if k != 0 { return t("not a function"); } let mut st: Vec = args.iter().map(|a| *a as u128).collect(); self.invoke(i, &mut st)?; Ok(st.into_iter().map(|v| v as u64).collect()) } fn ftype(&self, f: u32) -> FType { let ni = self.imports.len() as u32; if f < ni { self.types[self.imports[f as usize].2 as usize].clone() } else { self.types[self.funcs[(f - ni) as usize].ty as usize].clone() } } fn invoke(&mut self, f: u32, st: &mut Vec) -> Result<(), Trap> { let ty = self.ftype(f); let np = ty.params.len(); if st.len() < np { return t("stack underflow at call"); } let args: Vec = st.split_off(st.len() - np); let ni = self.imports.len() as u32; if f < ni { let mut h = self.host[f as usize].take().ok_or(Trap::Trap(format!("unbound import {}.{}", self.imports[f as usize].0, self.imports[f as usize].1)))?; let a64: Vec = args.iter().map(|a| *a as u64).collect(); let r = h(&mut self.mem, &a64); self.host[f as usize] = Some(h); let r = r?; if r.len() != ty.results.len() { return t("host result count"); } st.extend(r.into_iter().map(|v| v as u128)); return Ok(()); } self.depth += 1; if self.depth > 3000 { self.depth -= 1; return t("call stack exhausted"); } let r = self.run(f - ni, args); self.depth -= 1; let res = r?; st.extend(res); Ok(()) } /// An address operand (i32, or i64 under memory64) as an index. fn addr(&self, v: u128) -> u64 { if self.mem64 { v as u64 } else { v as u64 as u32 as u64 } } fn ea(&self, base: u128, off: u64, n: usize) -> Result { let a = self.addr(base).checked_add(off).ok_or(Trap::Trap("address overflow".into()))? as usize; if a.checked_add(n).map_or(true, |e| e > self.mem.len()) { return t("out of bounds memory access"); } Ok(a) } fn run(&mut self, fi: u32, args: Vec) -> Result, Trap> { let ty = self.types[self.funcs[fi as usize].ty as usize].clone(); let mut locals: Vec = args; locals.extend(std::iter::repeat(0).take(self.funcs[fi as usize].locals.len())); let code = self.funcs[fi as usize].code.clone(); let ctl = self.funcs[fi as usize].ctl.clone(); self.exec(&code, &ctl, &mut locals, ty.results.len()) } fn exec(&mut self, code: &[u8], ctl: &HashMap, l: &mut Vec, nres: usize) -> Result, Trap> { // label: (kind 0 block/if/try 1 loop, branch target pc, stack height, try catch label or MAX) struct Lab { lp: bool, target: usize, h: usize, catch: Option, /// legacy try: the catch_all body's pc legacy: usize, } let mut labs: Vec = Vec::new(); let mut st: Vec = Vec::new(); let mut r = Rd { b: code, p: ctl[&usize::MAX].0 }; macro_rules! pop { () => { st.pop().ok_or(Trap::Trap("stack underflow".into()))? }; } macro_rules! bin64 { ($f:expr) => {{ let b = pop!() as u64; let a = pop!() as u64; let f: fn(u64, u64) -> Result = $f; st.push(f(a, b)? as u128); }}; } macro_rules! bin32 { ($f:expr) => {{ let b = pop!() as u32; let a = pop!() as u32; let f: fn(u32, u32) -> Result = $f; st.push(f(a, b)? as u128); }}; } macro_rules! f64b { ($f:expr) => {{ let b = f64::from_bits(pop!() as u64); let a = f64::from_bits(pop!() as u64); let f: fn(f64, f64) -> f64 = $f; st.push(f(a, b).to_bits() as u128); }}; } macro_rules! f32b { ($f:expr) => {{ let b = f32::from_bits(pop!() as u32); let a = f32::from_bits(pop!() as u32); let f: fn(f32, f32) -> f32 = $f; st.push(f(a, b).to_bits() as u128); }}; } macro_rules! f64c { ($f:expr) => {{ let b = f64::from_bits(pop!() as u64); let a = f64::from_bits(pop!() as u64); let f: fn(f64, f64) -> bool = $f; st.push(f(a, b) as u128); }}; } macro_rules! f32c { ($f:expr) => {{ let b = f32::from_bits(pop!() as u32); let a = f32::from_bits(pop!() as u32); let f: fn(f32, f32) -> bool = $f; st.push(f(a, b) as u128); }}; } // branch to label depth d macro_rules! branch { ($d:expr) => {{ let d = $d as usize; if d >= labs.len() { // the function body's label: return let res = st.split_off(st.len() - nres); return Ok(res); } let k = labs.len() - 1 - d; let lab = &labs[k]; st.truncate(lab.h); r.p = lab.target; if lab.lp { labs.truncate(k + 1); } else { labs.truncate(k); } }}; } loop { self.steps += 1; if self.steps > self.max_steps { return t("step limit"); } if r.p >= code.len() { return t("ran off the code"); } let op = r.u8()?; let here = r.p; match op { 0x00 => return t("unreachable"), 0x01 => {} 0x02 | 0x03 | 0x04 => { let bt = r.s()?; if bt != -64 { return t("block type"); } let (els, end) = ctl[&here]; if op == 0x04 { let c = pop!() as u32; labs.push(Lab { lp: false, target: end, h: st.len(), catch: None, legacy: 0 }); if c == 0 { if els != 0 { r.p = els; } else { r.p = end; labs.pop(); } } } else if op == 0x03 { labs.push(Lab { lp: true, target: r.p, h: st.len(), catch: None, legacy: 0 }); } else { labs.push(Lab { lp: false, target: end, h: st.len(), catch: None, legacy: 0 }); } } 0x1F => { let bt = r.s()?; if bt != -64 { return t("try block type"); } let n = r.u()?; let mut catch = None; for _ in 0..n { let k = r.u8()?; if k != 2 { return t("only catch_all"); } catch = Some(r.u()? as u32); } let (_, end) = ctl[&here]; labs.push(Lab { lp: false, target: end, h: st.len(), catch, legacy: 0 }); } 0x06 => { if r.s()? != -64 { return t("try block type"); } let (cat, end) = ctl[&here]; if cat == 0 { return t("try without catch_all"); } labs.push(Lab { lp: false, target: end, h: st.len(), catch: None, legacy: cat }); } 0x05 | 0x19 => { // end of the then arm / try body: skip to after end let lab = labs.pop().ok_or(Trap::Trap("else".into()))?; r.p = lab.target; } 0x0B => { if labs.pop().is_none() { let res = st.split_off(st.len().checked_sub(nres).ok_or(Trap::Trap("results".into()))?); return Ok(res); } } 0x08 => { r.u()?; // find the innermost try in this frame let e = Trap::Exception; match labs.iter().rposition(|x| x.catch.is_some() || x.legacy != 0) { Some(k) if labs[k].legacy != 0 => { let h = labs[k].h; let cat = labs[k].legacy; labs.truncate(k + 1); labs[k].legacy = 0; st.truncate(h); r.p = cat; } Some(k) => { let c = labs[k].catch.unwrap(); let h = labs[k].h; labs.truncate(k); st.truncate(h); branch!(c); } None => return Err(e), } } 0x0C => { let d = r.u()?; branch!(d); } 0x0D => { let d = r.u()?; if pop!() as u32 != 0 { branch!(d); } } 0x0E => { let n = r.u()? as usize; let mut v = Vec::with_capacity(n + 1); for _ in 0..=n { v.push(r.u()?); } let i = pop!() as u32 as usize; let d = if i < n { v[i] } else { v[n] }; branch!(d); } 0x0F => { let res = st.split_off(st.len() - nres); return Ok(res); } 0x10 | 0x11 => { let callee = if op == 0x10 { r.u()? as u32 } else { let ti = r.u()? as u32; r.u()?; let i = pop!() as u32 as usize; let f = *self.table.get(i).ok_or(Trap::Trap("table index".into()))?; let f = f.ok_or(Trap::Trap("null table entry".into()))?; if self.ftype(f) != self.types[ti as usize] { return t("indirect call signature mismatch"); } f }; match self.invoke(callee, &mut st) { Ok(()) => {} Err(Trap::Exception) => match labs.iter().rposition(|x| x.catch.is_some() || x.legacy != 0) { Some(k) if labs[k].legacy != 0 => { let h = labs[k].h; let cat = labs[k].legacy; labs.truncate(k + 1); labs[k].legacy = 0; st.truncate(h); r.p = cat; } Some(k) => { let c = labs[k].catch.unwrap(); let h = labs[k].h; labs.truncate(k); st.truncate(h); branch!(c); } None => return Err(Trap::Exception), }, Err(e) => return Err(e), } } 0x1A => { pop!(); } 0x1B => { let c = pop!() as u32; let b = pop!(); let a = pop!(); st.push(if c != 0 { a } else { b }); } 0x20 => { let i = r.u()? as usize; st.push(l[i]); } 0x21 => { let i = r.u()? as usize; l[i] = pop!(); } 0x22 => { let i = r.u()? as usize; l[i] = *st.last().ok_or(Trap::Trap("tee".into()))?; } 0x23 => { let i = r.u()? as usize; st.push(self.globals[i] as u128); } 0x24 => { let i = r.u()? as usize; self.globals[i] = pop!() as u64; } 0x28..=0x35 => { let _al = r.u()?; let off = r.u()?; let base = pop!(); let n = match op { 0x28 | 0x2A | 0x34 | 0x35 => 4, 0x29 | 0x2B => 8, 0x2C | 0x2D | 0x30 | 0x31 => 1, _ => 2, }; let a = self.ea(base, off, n)?; let mut w = [0u8; 8]; w[..n].copy_from_slice(&self.mem[a..a + n]); let u = u64::from_le_bytes(w); let v: u64 = match op { 0x2C => u as u8 as i8 as i32 as u32 as u64, 0x2E => u as u16 as i16 as i32 as u32 as u64, 0x30 => u as u8 as i8 as i64 as u64, 0x32 => u as u16 as i16 as i64 as u64, 0x34 => u as u32 as i32 as i64 as u64, _ => u, }; st.push(v as u128); } 0x36..=0x3E => { let _al = r.u()?; let off = r.u()?; let v = pop!() as u64; let base = pop!(); let n = match op { 0x36 | 0x38 | 0x3E => 4, 0x37 | 0x39 => 8, 0x3A | 0x3C => 1, _ => 2, }; let a = self.ea(base, off, n)?; self.mem[a..a + n].copy_from_slice(&v.to_le_bytes()[..n]); } 0x41 => st.push(r.s()? as i32 as u32 as u128), 0x42 => st.push(r.s()? as u64 as u128), 0x43 => { let b = r.bytes(4)?; st.push(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as u128); } 0x44 => { let b = r.bytes(8)?; let mut w = [0u8; 8]; w.copy_from_slice(b); st.push(u64::from_le_bytes(w) as u128); } 0x45 => { let a = pop!() as u32; st.push((a == 0) as u128); } 0x46..=0x4F => bin32!(match op { 0x46 => |a, b| Ok((a == b) as u32), 0x47 => |a, b| Ok((a != b) as u32), 0x48 => |a, b| Ok(((a as i32) < (b as i32)) as u32), 0x49 => |a, b| Ok((a < b) as u32), 0x4A => |a, b| Ok((a as i32 > b as i32) as u32), 0x4B => |a, b| Ok((a > b) as u32), 0x4C => |a, b| Ok((a as i32 <= b as i32) as u32), 0x4D => |a, b| Ok((a <= b) as u32), 0x4E => |a, b| Ok((a as i32 >= b as i32) as u32), _ => |a, b| Ok((a >= b) as u32), }), 0x50 => { let a = pop!() as u64; st.push((a == 0) as u128); } 0x51..=0x5A => bin64!(match op { 0x51 => |a, b| Ok((a == b) as u64), 0x52 => |a, b| Ok((a != b) as u64), 0x53 => |a, b| Ok(((a as i64) < (b as i64)) as u64), 0x54 => |a, b| Ok((a < b) as u64), 0x55 => |a, b| Ok((a as i64 > b as i64) as u64), 0x56 => |a, b| Ok((a > b) as u64), 0x57 => |a, b| Ok((a as i64 <= b as i64) as u64), 0x58 => |a, b| Ok((a <= b) as u64), 0x59 => |a, b| Ok((a as i64 >= b as i64) as u64), _ => |a, b| Ok((a >= b) as u64), }), 0x5B..=0x60 => f32c!(match op { 0x5B => |a, b| a == b, 0x5C => |a, b| a != b, 0x5D => |a, b| a < b, 0x5E => |a, b| a > b, 0x5F => |a, b| a <= b, _ => |a, b| a >= b, }), 0x61..=0x66 => f64c!(match op { 0x61 => |a, b| a == b, 0x62 => |a, b| a != b, 0x63 => |a, b| a < b, 0x64 => |a, b| a > b, 0x65 => |a, b| a <= b, _ => |a, b| a >= b, }), 0x67..=0x69 => { let a = pop!() as u32; st.push(match op { 0x67 => a.leading_zeros(), 0x68 => a.trailing_zeros(), _ => a.count_ones(), } as u128); } 0x6A..=0x78 => bin32!(match op { 0x6A => |a, b| Ok(a.wrapping_add(b)), 0x6B => |a, b| Ok(a.wrapping_sub(b)), 0x6C => |a, b| Ok(a.wrapping_mul(b)), 0x6D => |a, b| if b == 0 || (a == 0x8000_0000 && b == u32::MAX) { t("int div") } else { Ok((a as i32 / b as i32) as u32) }, 0x6E => |a, b| if b == 0 { t("int div") } else { Ok(a / b) }, 0x6F => |a, b| if b == 0 { t("int div") } else { Ok((a as i32).wrapping_rem(b as i32) as u32) }, 0x70 => |a, b| if b == 0 { t("int div") } else { Ok(a % b) }, 0x71 => |a, b| Ok(a & b), 0x72 => |a, b| Ok(a | b), 0x73 => |a, b| Ok(a ^ b), 0x74 => |a, b| Ok(a << (b & 31)), 0x75 => |a, b| Ok(((a as i32) >> (b & 31)) as u32), 0x76 => |a, b| Ok(a >> (b & 31)), 0x77 => |a, b| Ok(a.rotate_left(b & 31)), _ => |a, b| Ok(a.rotate_right(b & 31)), }), 0x79..=0x7B => { let a = pop!() as u64; st.push(match op { 0x79 => a.leading_zeros(), 0x7A => a.trailing_zeros(), _ => a.count_ones(), } as u128); } 0x7C..=0x8A => bin64!(match op { 0x7C => |a, b| Ok(a.wrapping_add(b)), 0x7D => |a, b| Ok(a.wrapping_sub(b)), 0x7E => |a, b| Ok(a.wrapping_mul(b)), 0x7F => |a, b| if b == 0 || (a == 1 << 63 && b == u64::MAX) { t("int div") } else { Ok((a as i64 / b as i64) as u64) }, 0x80 => |a, b| if b == 0 { t("int div") } else { Ok(a / b) }, 0x81 => |a, b| if b == 0 { t("int div") } else { Ok((a as i64).wrapping_rem(b as i64) as u64) }, 0x82 => |a, b| if b == 0 { t("int div") } else { Ok(a % b) }, 0x83 => |a, b| Ok(a & b), 0x84 => |a, b| Ok(a | b), 0x85 => |a, b| Ok(a ^ b), 0x86 => |a, b| Ok(a << (b & 63)), 0x87 => |a, b| Ok(((a as i64) >> (b & 63)) as u64), 0x88 => |a, b| Ok(a >> (b & 63)), 0x89 => |a, b| Ok(a.rotate_left((b & 63) as u32)), _ => |a, b| Ok(a.rotate_right((b & 63) as u32)), }), 0x8B..=0x91 => { let a = f32::from_bits(pop!() as u32); let v = match op { 0x8B => a.abs(), 0x8C => -a, 0x8D => a.ceil(), 0x8E => a.floor(), 0x8F => a.trunc(), 0x90 => a.round_ties_even(), _ => a.sqrt(), }; st.push(v.to_bits() as u128); } 0x92..=0x98 => f32b!(match op { 0x92 => |a, b| a + b, 0x93 => |a, b| a - b, 0x94 => |a, b| a * b, 0x95 => |a, b| a / b, 0x96 => |a, b| if a.is_nan() || b.is_nan() { f32::NAN } else if a == b { if a.is_sign_negative() { a } else { b } } else { a.min(b) }, 0x97 => |a, b| if a.is_nan() || b.is_nan() { f32::NAN } else if a == b { if a.is_sign_positive() { a } else { b } } else { a.max(b) }, _ => |a, b| a.copysign(b), }), 0x99..=0x9F => { let a = f64::from_bits(pop!() as u64); let v = match op { 0x99 => a.abs(), 0x9A => -a, 0x9B => a.ceil(), 0x9C => a.floor(), 0x9D => a.trunc(), 0x9E => a.round_ties_even(), _ => a.sqrt(), }; st.push(v.to_bits() as u128); } 0xA0..=0xA6 => f64b!(match op { 0xA0 => |a, b| a + b, 0xA1 => |a, b| a - b, 0xA2 => |a, b| a * b, 0xA3 => |a, b| a / b, 0xA4 => |a, b| if a.is_nan() || b.is_nan() { f64::NAN } else if a == b { if a.is_sign_negative() { a } else { b } } else { a.min(b) }, 0xA5 => |a, b| if a.is_nan() || b.is_nan() { f64::NAN } else if a == b { if a.is_sign_positive() { a } else { b } } else { a.max(b) }, _ => |a, b| a.copysign(b), }), 0xA7 => { let a = pop!() as u64; st.push(a as u32 as u128); } 0xAC => { let a = pop!() as u32; st.push(a as i32 as i64 as u64 as u128); } 0xAD => { let a = pop!() as u32; st.push(a as u128); } 0xB2..=0xBA => { let a = pop!(); let v: u64 = match op { 0xB2 => (a as u32 as i32 as f32).to_bits() as u64, 0xB3 => (a as u32 as f32).to_bits() as u64, 0xB4 => (a as u64 as i64 as f32).to_bits() as u64, 0xB5 => (a as u64 as f32).to_bits() as u64, 0xB6 => (f64::from_bits(a as u64) as f32).to_bits() as u64, 0xB7 => (a as u32 as i32 as f64).to_bits(), 0xB8 => (a as u32 as f64).to_bits(), 0xB9 => (a as u64 as i64 as f64).to_bits(), _ => (a as u64 as f64).to_bits(), }; st.push(v as u128); } 0xBB => { let a = f32::from_bits(pop!() as u32); st.push((a as f64).to_bits() as u128); } 0xBC | 0xBE => { let a = pop!() as u32; st.push(a as u128); } 0xBD | 0xBF => { let a = pop!() as u64; st.push(a as u128); } 0xC0..=0xC4 => { let a = pop!(); let v: u64 = match op { 0xC0 => a as u8 as i8 as i32 as u32 as u64, 0xC1 => a as u16 as i16 as i32 as u32 as u64, 0xC2 => a as u8 as i8 as i64 as u64, 0xC3 => a as u16 as i16 as i64 as u64, _ => a as u32 as i32 as i64 as u64, }; st.push(v as u128); } 0xFC => { let k = r.u()?; match k { 0..=7 => { let a = pop!(); let x = if k & 2 == 0 { f32::from_bits(a as u32) as f64 } else { f64::from_bits(a as u64) }; let v: u64 = match k { 0 | 2 => x as i32 as u32 as u64, 1 | 3 => x as u32 as u64, 4 | 6 => x as i64 as u64, _ => x as u64, }; st.push(v as u128); } 10 => { r.u8()?; r.u8()?; let n = self.addr(pop!()) as usize; let s = self.addr(pop!()) as usize; let d = self.addr(pop!()) as usize; if s + n > self.mem.len() || d + n > self.mem.len() { return t("out of bounds memory access"); } self.mem.copy_within(s..s + n, d); } 11 => { r.u8()?; let n = self.addr(pop!()) as usize; let v = pop!() as u8; let d = self.addr(pop!()) as usize; if d + n > self.mem.len() { return t("out of bounds memory access"); } self.mem[d..d + n].fill(v); } _ => return t(&format!("0xFC {}", k)), } } 0xFD => { let k = r.u()?; let lanes32 = |v: u128| -> [u32; 4] { [v as u32, (v >> 32) as u32, (v >> 64) as u32, (v >> 96) as u32] }; let from32 = |a: [u32; 4]| -> u128 { a[0] as u128 | (a[1] as u128) << 32 | (a[2] as u128) << 64 | (a[3] as u128) << 96 }; match k { 0 => { r.u()?; let off = r.u()?; let base = pop!(); let a = self.ea(base, off, 16)?; let mut w = [0u8; 16]; w.copy_from_slice(&self.mem[a..a + 16]); st.push(u128::from_le_bytes(w)); } 11 => { r.u()?; let off = r.u()?; let v = pop!(); let base = pop!(); let a = self.ea(base, off, 16)?; self.mem[a..a + 16].copy_from_slice(&v.to_le_bytes()); } 12 => { let b = r.bytes(16)?; let mut w = [0u8; 16]; w.copy_from_slice(b); st.push(u128::from_le_bytes(w)); } 17 | 19 => { let a = pop!() as u32; st.push(from32([a; 4])); } 27 | 31 => { let ln = r.u8()? as usize; let v = pop!(); st.push(lanes32(v)[ln] as u128); } 28 | 32 => { let ln = r.u8()? as usize; let s = pop!() as u32; let v = pop!(); let mut a = lanes32(v); a[ln] = s; st.push(from32(a)); } 78..=81 => { let b = pop!(); let a = pop!(); st.push(match k { 78 => a & b, 79 => a & !b, 80 => a | b, _ => a ^ b, }); } 82 => { let c = pop!(); let b = pop!(); let a = pop!(); st.push((a & c) | (b & !c)); } 83 => { let a = pop!(); st.push((a != 0) as u128); } 174 | 177 | 181 | 228..=231 => { let b = lanes32(pop!()); let a = lanes32(pop!()); let mut o = [0u32; 4]; for i in 0..4 { let (x, y) = (a[i], b[i]); let (fx, fy) = (f32::from_bits(x), f32::from_bits(y)); o[i] = match k { 174 => x.wrapping_add(y), 177 => x.wrapping_sub(y), 181 => x.wrapping_mul(y), 228 => (fx + fy).to_bits(), 229 => (fx - fy).to_bits(), 230 => (fx * fy).to_bits(), _ => (fx / fy).to_bits(), }; } st.push(from32(o)); } _ => return t(&format!("simd op {}", k)), } } _ => return t(&format!("opcode {:#x}", op)), } } } }