use parity_wasm::elements::{ BlockType, External, Func, FuncBody, FunctionSection, FunctionType, ImportEntry, ImportSection, Instruction, Module, Type, TypeSection, }; use crate::{ node::{ Backward, BinOp, BinOpType, Br, BrIf, BrTable, Call, CallIndirect, CmpOp, CmpOpType, Expression, Forward, FuncData, GetGlobal, GetLocal, If, LoadAt, LoadType, MemoryGrow, MemorySize, Select, SetGlobal, SetLocal, Statement, StoreAt, StoreType, Terminator, UnOp, UnOpType, Value, }, stack::{ReadType, Stack}, }; macro_rules! leak_on { ($name:tt, $variant:tt) => { fn $name(&mut self, id: usize) { let read = ReadType::$variant(id); self.stack.leak_into(&mut self.code, |v| v.has_read(read)) } }; } struct Arity { num_param: usize, num_result: usize, } impl Arity { fn from_type(typ: &FunctionType) -> Self { Self { num_param: typ.params().len(), num_result: typ.results().len(), } } } pub struct TypeInfo<'a> { data: &'a [Type], func_ex: Vec, func_in: Vec, } impl<'a> TypeInfo<'a> { #[must_use] pub fn from_module(parent: &'a Module) -> Self { let data = parent .type_section() .map_or([].as_ref(), TypeSection::types); let func_ex = Self::new_ex_list(parent); let func_in = Self::new_in_list(parent); Self { data, func_ex, func_in, } } #[must_use] pub fn len_in(&self) -> usize { self.func_in.len() } #[must_use] pub fn len_ex(&self) -> usize { self.func_ex.len() } fn arity_of(&self, index: usize) -> Arity { let Type::Function(typ) = &self.data[index]; Arity::from_type(typ) } fn rel_arity_of(&self, index: usize) -> Arity { let adjusted = self .func_ex .iter() .chain(self.func_in.iter()) .nth(index) .unwrap(); self.arity_of(*adjusted) } fn block_arity_of(&self, typ: BlockType) -> Arity { match typ { BlockType::NoResult => Arity { num_param: 0, num_result: 0, }, BlockType::Value(_) => Arity { num_param: 0, num_result: 1, }, BlockType::TypeIndex(i) => { let id = i.try_into().unwrap(); self.arity_of(id) } } } fn func_of_import(import: &ImportEntry) -> Option { if let &External::Function(i) = import.external() { Some(i.try_into().unwrap()) } else { None } } fn new_ex_list(wasm: &Module) -> Vec { let list = wasm .import_section() .map_or([].as_ref(), ImportSection::entries); list.iter().filter_map(Self::func_of_import).collect() } fn new_in_list(wasm: &Module) -> Vec { let list = wasm .function_section() .map_or([].as_ref(), FunctionSection::entries); list.iter() .map(Func::type_ref) .map(|v| v.try_into().unwrap()) .collect() } } #[derive(Clone, Copy)] enum BlockVariant { Forward, Backward, If, Else, } enum BlockData { Forward { num_result: usize }, Backward { num_param: usize }, If { num_result: usize, typ: BlockType }, Else { num_result: usize }, } impl Default for BlockData { fn default() -> Self { BlockData::Forward { num_result: 0 } } } #[derive(Default)] struct StatList { stack: Stack, code: Vec, last: Option, block_data: BlockData, } impl StatList { fn new() -> Self { Self::default() } fn leak_all(&mut self) { self.stack.leak_into(&mut self.code, |_| true); } fn leak_pre_call(&mut self) { self.stack.leak_into(&mut self.code, |v| { v.has_global_read() || v.has_memory_read() }); } leak_on!(leak_local_write, Local); leak_on!(leak_global_write, Global); leak_on!(leak_memory_write, Memory); fn push_load(&mut self, what: LoadType, offset: u32) { let data = Expression::LoadAt(LoadAt { what, offset, pointer: self.stack.pop().into(), }); self.stack.push_with_single(data); } fn add_store(&mut self, what: StoreType, offset: u32) { let data = Statement::StoreAt(StoreAt { what, offset, value: self.stack.pop(), pointer: self.stack.pop(), }); self.leak_memory_write(0); self.code.push(data); } fn push_constant>(&mut self, value: T) { let value = Expression::Value(value.into()); self.stack.push(value); } fn push_un_op(&mut self, op: UnOpType) { let rhs = self.stack.pop_with_read(); let data = Expression::UnOp(UnOp { op, rhs: rhs.0.into(), }); self.stack.push_with_read(data, rhs.1); } fn push_bin_op(&mut self, op: BinOpType) { let mut rhs = self.stack.pop_with_read(); let lhs = self.stack.pop_with_read(); let data = Expression::BinOp(BinOp { op, rhs: rhs.0.into(), lhs: lhs.0.into(), }); rhs.1.extend(lhs.1); self.stack.push_with_read(data, rhs.1); } fn push_cmp_op(&mut self, op: CmpOpType) { let mut rhs = self.stack.pop_with_read(); let lhs = self.stack.pop_with_read(); let data = Expression::CmpOp(CmpOp { op, rhs: rhs.0.into(), lhs: lhs.0.into(), }); rhs.1.extend(lhs.1); self.stack.push_with_read(data, rhs.1); } // Eqz is the only unary comparison so it's "emulated" // using a constant operand fn try_add_equal_zero(&mut self, inst: &Instruction) -> bool { match inst { Instruction::I32Eqz => { self.push_constant(0_i32); self.push_cmp_op(CmpOpType::Eq_I32); true } Instruction::I64Eqz => { self.push_constant(0_i64); self.push_cmp_op(CmpOpType::Eq_I64); true } _ => false, } } // Try to generate a simple operation fn try_add_operation(&mut self, inst: &Instruction) -> bool { if let Ok(op) = UnOpType::try_from(inst) { self.push_un_op(op); true } else if let Ok(op) = BinOpType::try_from(inst) { self.push_bin_op(op); true } else if let Ok(op) = CmpOpType::try_from(inst) { self.push_cmp_op(op); true } else { self.try_add_equal_zero(inst) } } fn set_terminator(&mut self, term: Terminator) { self.leak_all(); self.last = Some(term); } } impl From for Forward { fn from(stat: StatList) -> Self { Self { code: stat.code, last: stat.last, } } } impl From for Backward { fn from(stat: StatList) -> Self { Self { code: stat.code, last: stat.last, } } } pub struct Builder<'a> { type_info: &'a TypeInfo<'a>, pending: Vec, target: StatList, nested_unreachable: usize, } impl<'a> Builder<'a> { #[must_use] pub fn from_type_info(type_info: &'a TypeInfo<'a>) -> Self { Self { type_info, pending: Vec::new(), target: StatList::new(), nested_unreachable: 0, } } #[must_use] pub fn build_anonymous(&mut self, list: &[Instruction]) -> FuncData { let data = self.build_stat_list(list, 1); FuncData { local_data: Vec::new(), num_result: 1, num_param: 0, num_stack: data.stack.capacity, code: data.into(), } } #[must_use] pub fn build_indexed(&mut self, index: usize, func: &FuncBody) -> FuncData { let arity = &self.type_info.rel_arity_of(self.type_info.len_ex() + index); let data = self.build_stat_list(func.code().elements(), arity.num_result); FuncData { local_data: func.locals().to_vec(), num_result: arity.num_result, num_param: arity.num_param, num_stack: data.stack.capacity, code: data.into(), } } fn start_block(&mut self, typ: BlockType, variant: BlockVariant) { let Arity { num_param, num_result, } = self.type_info.block_arity_of(typ); let mut old = std::mem::take(&mut self.target); old.leak_all(); self.target.block_data = match variant { BlockVariant::Forward => BlockData::Forward { num_result }, BlockVariant::Backward => BlockData::Backward { num_param }, BlockVariant::If => BlockData::If { num_result, typ }, BlockVariant::Else => { old.stack.pop_len(num_result).for_each(drop); old.stack.push_temporary(num_param); BlockData::Else { num_result } } }; self.target.stack = old.stack.split_last(num_param); old.stack.push_temporary(num_result); self.pending.push(old); } fn start_else(&mut self) { let typ = if let BlockData::If { typ, .. } = self.target.block_data { typ } else { unreachable!() }; self.target.leak_all(); self.end_block(); self.start_block(typ, BlockVariant::Else); } fn end_block(&mut self) { let old = self.pending.pop().unwrap(); let now = std::mem::replace(&mut self.target, old); self.target.stack.capacity = now.stack.capacity; let stat = match now.block_data { BlockData::Forward { .. } => Statement::Forward(now.into()), BlockData::Backward { .. } => Statement::Backward(now.into()), BlockData::If { .. } => Statement::If(If { cond: self.target.stack.pop(), truthy: now.into(), falsey: None, }), BlockData::Else { .. } => { if let Statement::If(v) = self.target.code.last_mut().unwrap() { v.falsey = Some(now.into()); } else { unreachable!() } return; } }; self.target.code.push(stat); } fn get_relative_block(&self, index: usize) -> &StatList { if index == 0 { &self.target } else { &self.pending[self.pending.len() - index] } } fn get_br_terminator(&self, target: usize) -> Br { let block = self.get_relative_block(target); let par_result = match block.block_data { BlockData::Forward { num_result } | BlockData::If { num_result, .. } | BlockData::Else { num_result } => num_result, BlockData::Backward { num_param } => num_param, }; let align = self .target .stack .get_br_alignment(block.stack.previous, par_result); Br { target, align } } fn add_call(&mut self, func: usize) { let arity = self.type_info.rel_arity_of(func); let param_list = self.target.stack.pop_len(arity.num_param).collect(); self.target.leak_pre_call(); let result = self.target.stack.push_temporary(arity.num_result); let data = Statement::Call(Call { func, result, param_list, }); self.target.code.push(data); } fn add_call_indirect(&mut self, typ: usize, table: usize) { let arity = self.type_info.arity_of(typ); let index = self.target.stack.pop(); let param_list = self.target.stack.pop_len(arity.num_param).collect(); self.target.leak_pre_call(); let result = self.target.stack.push_temporary(arity.num_result); let data = Statement::CallIndirect(CallIndirect { table, index, result, param_list, }); self.target.code.push(data); } #[cold] fn drop_unreachable(&mut self, inst: &Instruction) { match inst { Instruction::Block(_) | Instruction::Loop(_) | Instruction::If(_) => { self.nested_unreachable += 1; } Instruction::Else if self.nested_unreachable == 1 => { self.nested_unreachable -= 1; self.start_else(); } Instruction::End if self.nested_unreachable == 1 => { self.nested_unreachable -= 1; self.end_block(); } Instruction::End => { self.nested_unreachable -= 1; } _ => {} } } #[allow(clippy::too_many_lines)] fn add_instruction(&mut self, inst: &Instruction) { use Instruction as Inst; if self.target.try_add_operation(inst) { return; } match *inst { Inst::Unreachable => { self.nested_unreachable += 1; self.target.set_terminator(Terminator::Unreachable); } Inst::Nop => {} Inst::Block(typ) => { self.start_block(typ, BlockVariant::Forward); } Inst::Loop(typ) => { self.start_block(typ, BlockVariant::Backward); } Inst::If(typ) => { let cond = self.target.stack.pop(); self.start_block(typ, BlockVariant::If); self.pending.last_mut().unwrap().stack.push(cond); } Inst::Else => { self.start_else(); } Inst::End => { self.target.leak_all(); self.end_block(); } Inst::Br(v) => { let target = v.try_into().unwrap(); let term = Terminator::Br(self.get_br_terminator(target)); self.target.set_terminator(term); self.nested_unreachable += 1; } Inst::BrIf(v) => { let data = Statement::BrIf(BrIf { cond: self.target.stack.pop(), target: self.get_br_terminator(v.try_into().unwrap()), }); self.target.leak_all(); self.target.code.push(data); } Inst::BrTable(ref v) => { let cond = self.target.stack.pop(); let data = v .table .iter() .copied() .map(|v| self.get_br_terminator(v.try_into().unwrap())) .collect(); let default = self.get_br_terminator(v.default.try_into().unwrap()); let term = Terminator::BrTable(BrTable { cond, data, default, }); self.target.set_terminator(term); self.nested_unreachable += 1; } Inst::Return => { let target = self.pending.len(); let term = Terminator::Br(self.get_br_terminator(target)); self.target.set_terminator(term); self.nested_unreachable += 1; } Inst::Call(i) => { self.add_call(i.try_into().unwrap()); } Inst::CallIndirect(i, t) => { self.add_call_indirect(i.try_into().unwrap(), t.into()); } Inst::Drop => { self.target.stack.pop(); } Inst::Select => { let mut cond = self.target.stack.pop_with_read(); let b = self.target.stack.pop_with_read(); let a = self.target.stack.pop_with_read(); let data = Expression::Select(Select { cond: cond.0.into(), b: b.0.into(), a: a.0.into(), }); cond.1.extend(b.1); cond.1.extend(a.1); self.target.stack.push_with_read(data, cond.1); } Inst::GetLocal(i) => { let var = i.try_into().unwrap(); let data = Expression::GetLocal(GetLocal { var }); self.target.stack.push_with_single(data); } Inst::SetLocal(i) => { let var = i.try_into().unwrap(); let data = Statement::SetLocal(SetLocal { var, value: self.target.stack.pop(), }); self.target.leak_local_write(var); self.target.code.push(data); } Inst::TeeLocal(i) => { let var = i.try_into().unwrap(); let get = Expression::GetLocal(GetLocal { var }); let set = Statement::SetLocal(SetLocal { var, value: self.target.stack.pop(), }); self.target.leak_local_write(var); self.target.stack.push_with_single(get); self.target.code.push(set); } Inst::GetGlobal(i) => { let var = i.try_into().unwrap(); let data = Expression::GetGlobal(GetGlobal { var }); self.target.stack.push_with_single(data); } Inst::SetGlobal(i) => { let var = i.try_into().unwrap(); let data = Statement::SetGlobal(SetGlobal { var, value: self.target.stack.pop(), }); self.target.leak_global_write(var); self.target.code.push(data); } Inst::I32Load(_, o) => self.target.push_load(LoadType::I32, o), Inst::I64Load(_, o) => self.target.push_load(LoadType::I64, o), Inst::F32Load(_, o) => self.target.push_load(LoadType::F32, o), Inst::F64Load(_, o) => self.target.push_load(LoadType::F64, o), Inst::I32Load8S(_, o) => self.target.push_load(LoadType::I32_I8, o), Inst::I32Load8U(_, o) => self.target.push_load(LoadType::I32_U8, o), Inst::I32Load16S(_, o) => self.target.push_load(LoadType::I32_I16, o), Inst::I32Load16U(_, o) => self.target.push_load(LoadType::I32_U16, o), Inst::I64Load8S(_, o) => self.target.push_load(LoadType::I64_I8, o), Inst::I64Load8U(_, o) => self.target.push_load(LoadType::I64_U8, o), Inst::I64Load16S(_, o) => self.target.push_load(LoadType::I64_I16, o), Inst::I64Load16U(_, o) => self.target.push_load(LoadType::I64_U16, o), Inst::I64Load32S(_, o) => self.target.push_load(LoadType::I64_I32, o), Inst::I64Load32U(_, o) => self.target.push_load(LoadType::I64_U32, o), Inst::I32Store(_, o) => self.target.add_store(StoreType::I32, o), Inst::I64Store(_, o) => self.target.add_store(StoreType::I64, o), Inst::F32Store(_, o) => self.target.add_store(StoreType::F32, o), Inst::F64Store(_, o) => self.target.add_store(StoreType::F64, o), Inst::I32Store8(_, o) => self.target.add_store(StoreType::I32_N8, o), Inst::I32Store16(_, o) => self.target.add_store(StoreType::I32_N16, o), Inst::I64Store8(_, o) => self.target.add_store(StoreType::I64_N8, o), Inst::I64Store16(_, o) => self.target.add_store(StoreType::I64_N16, o), Inst::I64Store32(_, o) => self.target.add_store(StoreType::I64_N32, o), Inst::CurrentMemory(i) => { let memory = i.try_into().unwrap(); let data = Expression::MemorySize(MemorySize { memory }); self.target.stack.push(data); } Inst::GrowMemory(i) => { let value = self.target.stack.pop().into(); let result = self.target.stack.push_temporary(1).start; let memory = i.try_into().unwrap(); let data = Statement::MemoryGrow(MemoryGrow { result, memory, value, }); self.target.leak_memory_write(memory); self.target.code.push(data); } Inst::I32Const(v) => self.target.push_constant(v), Inst::I64Const(v) => self.target.push_constant(v), Inst::F32Const(v) => self.target.push_constant(v), Inst::F64Const(v) => self.target.push_constant(v), Inst::SignExt(_) => todo!(), _ => unreachable!(), } } fn build_stat_list(&mut self, list: &[Instruction], num_result: usize) -> StatList { self.target.block_data = BlockData::Forward { num_result }; self.nested_unreachable = 0; for inst in list.iter().take(list.len() - 1) { if self.nested_unreachable == 0 { self.add_instruction(inst); } else { self.drop_unreachable(inst); } } if self.nested_unreachable == 0 { self.target.leak_all(); } std::mem::take(&mut self.target) } }