diff --git a/codegen-luajit/src/translator.rs b/codegen-luajit/src/translator.rs index 9ade6be..6281c23 100644 --- a/codegen-luajit/src/translator.rs +++ b/codegen-luajit/src/translator.rs @@ -49,7 +49,7 @@ fn write_named_array(name: &str, len: usize, w: &mut dyn Write) -> Result<()> { } fn write_constant(code: &[Instruction], type_info: &TypeInfo, w: &mut dyn Write) -> Result<()> { - let func = Builder::new(type_info).with_anon(code); + let func = Builder::from_type_info(type_info).build_anonymous(code); write!(w, "(")?; func.write(&mut Manager::default(), w)?; @@ -226,7 +226,7 @@ fn build_func_list(wasm: &Module, type_info: &TypeInfo) -> Vec { let iter = list.iter().enumerate(); - iter.map(|f| Builder::new(type_info).with_index(f.0, f.1)) + iter.map(|f| Builder::from_type_info(type_info).build_indexed(f.0, f.1)) .collect() } @@ -326,8 +326,8 @@ fn write_module_start( /// # Errors /// Returns `Err` if writing to `Write` failed. pub fn from_inst_list(code: &[Instruction], type_info: &TypeInfo, w: &mut dyn Write) -> Result<()> { - Builder::new(type_info) - .with_anon(code) + Builder::from_type_info(type_info) + .build_anonymous(code) .write(&mut Manager::default(), w) } diff --git a/codegen-luau/src/translator.rs b/codegen-luau/src/translator.rs index bcba775..d9ff6c6 100644 --- a/codegen-luau/src/translator.rs +++ b/codegen-luau/src/translator.rs @@ -48,7 +48,7 @@ fn write_named_array(name: &str, len: usize, w: &mut dyn Write) -> Result<()> { } fn write_constant(code: &[Instruction], type_info: &TypeInfo, w: &mut dyn Write) -> Result<()> { - let func = Builder::new(type_info).with_anon(code); + let func = Builder::from_type_info(type_info).build_anonymous(code); write!(w, "(")?; func.write(&mut Manager::default(), w)?; @@ -225,7 +225,7 @@ fn build_func_list(wasm: &Module, type_info: &TypeInfo) -> Vec { let iter = list.iter().enumerate(); - iter.map(|f| Builder::new(type_info).with_index(f.0, f.1)) + iter.map(|f| Builder::from_type_info(type_info).build_indexed(f.0, f.1)) .collect() } @@ -324,8 +324,8 @@ fn write_module_start( /// # Errors /// Returns `Err` if writing to `Write` failed. pub fn from_inst_list(code: &[Instruction], type_info: &TypeInfo, w: &mut dyn Write) -> Result<()> { - Builder::new(type_info) - .with_anon(code) + Builder::from_type_info(type_info) + .build_anonymous(code) .write(&mut Manager::default(), w) } diff --git a/wasm-ast/src/builder.rs b/wasm-ast/src/builder.rs index bc64d50..fe4a110 100644 --- a/wasm-ast/src/builder.rs +++ b/wasm-ast/src/builder.rs @@ -10,6 +10,14 @@ use crate::node::{ StoreType, UnOp, UnOpType, Value, }; +macro_rules! leak_with_predicate { + ($name:tt, $predicate:tt) => { + fn $name(&mut self, id: usize) { + self.leak_with(|v| v.$predicate(id)); + } + }; +} + struct Arity { num_param: usize, num_result: usize, @@ -57,13 +65,13 @@ impl<'a> TypeInfo<'a> { self.func_ex.len() } - fn raw_arity_of(&self, index: usize) -> Arity { + fn arity_of(&self, index: usize) -> Arity { let Type::Function(typ) = &self.data[index]; Arity::from_type(typ) } - fn arity_of(&self, index: usize) -> Arity { + fn rel_arity_of(&self, index: usize) -> Arity { let adjusted = self .func_ex .iter() @@ -71,7 +79,7 @@ impl<'a> TypeInfo<'a> { .nth(index) .unwrap(); - self.raw_arity_of(*adjusted) + self.arity_of(*adjusted) } fn func_of_import(import: &ImportEntry) -> Option { @@ -103,126 +111,144 @@ impl<'a> TypeInfo<'a> { } #[derive(Default)] -struct Stacked { - pending_list: Vec>, +struct StatList { + code: Vec, stack: Vec, - last_stack: usize, + + num_result: usize, + num_stack: usize, + is_else: bool, } -impl Stacked { +impl StatList { fn new() -> Self { Self::default() } - // If any expressions are still pending at the start of - // statement, we leak them into variables. - // Since expressions do not have set ordering rules, this is - // safe and condenses code. - fn gen_leak_pending(&mut self, stat: &mut Vec) { - self.last_stack = self.last_stack.max(self.stack.len()); - - for (i, v) in self - .stack - .iter_mut() - .enumerate() - .filter(|v| !v.1.is_temporary(v.0)) - { - let get = Expression::GetTemporary(GetTemporary { var: i }); - let set = Statement::SetTemporary(SetTemporary { - var: i, - value: std::mem::replace(v, get), - }); - - stat.push(set); - } - } - - // Pending expressions are put to sleep before entering - // a control structure so that they are not lost. - fn save_pending(&mut self) { - let cloned = self.stack.iter().map(Expression::clone_temporary).collect(); - - self.pending_list.push(cloned); - } - - fn load_pending(&mut self) { - self.stack = self.pending_list.pop().unwrap(); - } - - fn pop(&mut self) -> Expression { + fn pop_required(&mut self) -> Expression { self.stack.pop().unwrap() } - fn pop_many(&mut self, len: usize) -> Vec { + fn pop_len(&mut self, len: usize) -> Vec { self.stack.split_off(self.stack.len() - len) } - fn push(&mut self, value: Expression) { - self.stack.push(value); + fn push_tracked(&mut self, data: Expression) { + self.stack.push(data); + self.num_stack = self.num_stack.max(self.stack.len()); } - fn push_constant>(&mut self, value: T) { - let value = Expression::Value(value.into()); + fn leak_at(&mut self, var: usize) { + let old = self.stack.get_mut(var).unwrap(); - self.stack.push(value); + if old.is_temporary(var) { + return; + } + + let get = Expression::GetTemporary(GetTemporary { var }); + let set = Statement::SetTemporary(SetTemporary { + var, + value: std::mem::replace(old, get), + }); + + self.code.push(set); + } + + fn leak_with

(&mut self, predicate: P) + where + P: Fn(&Expression) -> bool, + { + let pend: Vec<_> = self + .stack + .iter() + .enumerate() + .filter_map(|v| predicate(v.1).then(|| v.0)) + .collect(); + + for var in pend { + self.leak_at(var); + } + } + + leak_with_predicate!(leak_local_write, is_local_read); + leak_with_predicate!(leak_global_write, is_global_read); + leak_with_predicate!(leak_memory_size, is_memory_size); + leak_with_predicate!(leak_memory_write, is_memory_ref); + + fn leak_all(&mut self) { + self.leak_with(|_| true); } fn push_temporary(&mut self, num: usize) { let len = self.stack.len(); for var in len..len + num { - self.stack - .push(Expression::GetTemporary(GetTemporary { var })); + let data = Expression::GetTemporary(GetTemporary { var }); + + self.push_tracked(data); } } fn push_load(&mut self, what: LoadType, offset: u32) { - let pointer = Box::new(self.pop()); - - self.stack.push(Expression::LoadAt(LoadAt { + let data = Expression::LoadAt(LoadAt { what, offset, - pointer, - })); + pointer: self.pop_required().into(), + }); + + self.push_tracked(data); } - fn gen_store(&mut self, what: StoreType, offset: u32, stat: &mut Vec) { - let value = self.pop(); - let pointer = self.pop(); - - self.gen_leak_pending(stat); - - stat.push(Statement::StoreAt(StoreAt { + fn add_store(&mut self, what: StoreType, offset: u32) { + let data = Statement::StoreAt(StoreAt { what, offset, - pointer, - value, - })); + value: self.pop_required(), + pointer: self.pop_required(), + }); + + self.leak_memory_write(0); + self.code.push(data); + } + + fn push_constant>(&mut self, value: T) { + let value = Expression::Value(value.into()); + + self.push_tracked(value); } fn push_un_op(&mut self, op: UnOpType) { - let rhs = Box::new(self.pop()); + let data = Expression::UnOp(UnOp { + op, + rhs: self.pop_required().into(), + }); - self.stack.push(Expression::UnOp(UnOp { op, rhs })); + self.push_tracked(data); } fn push_bin_op(&mut self, op: BinOpType) { - let rhs = Box::new(self.pop()); - let lhs = Box::new(self.pop()); + let data = Expression::BinOp(BinOp { + op, + rhs: self.pop_required().into(), + lhs: self.pop_required().into(), + }); - self.stack.push(Expression::BinOp(BinOp { op, lhs, rhs })); + self.push_tracked(data); } fn push_cmp_op(&mut self, op: CmpOpType) { - let rhs = Box::new(self.pop()); - let lhs = Box::new(self.pop()); + let data = Expression::CmpOp(CmpOp { + op, + rhs: self.pop_required().into(), + lhs: self.pop_required().into(), + }); - self.stack.push(Expression::CmpOp(CmpOp { op, lhs, rhs })); + self.push_tracked(data); } - // Since Eqz is the only unary comparison it's cleaner to - // generate a simple CmpOp - fn try_equal_zero(&mut self, inst: &Instruction) -> bool { + // 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); @@ -240,7 +266,8 @@ impl Stacked { } } - fn try_operation(&mut self, inst: &Instruction) -> bool { + // 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); @@ -254,382 +281,379 @@ impl Stacked { true } else { - self.try_equal_zero(inst) + self.try_add_equal_zero(inst) } } } pub struct Builder<'a> { - // target state type_info: &'a TypeInfo<'a>, + + pending: Vec, + target: StatList, + num_result: usize, - - // translation state - data: Stacked, -} - -fn is_else_stat(inst: &Instruction) -> bool { - inst == &Instruction::Else -} - -fn is_dead_precursor(inst: &Instruction) -> bool { - matches!( - inst, - Instruction::Unreachable | Instruction::Br(_) | Instruction::Return - ) + nested_unreachable: usize, } impl<'a> Builder<'a> { #[must_use] - pub fn new(info: &'a TypeInfo) -> Builder<'a> { - Builder { - type_info: info, + pub fn from_type_info(type_info: &'a TypeInfo<'a>) -> Self { + Self { + type_info, + pending: Vec::new(), + target: StatList::new(), num_result: 0, - data: Stacked::new(), + nested_unreachable: 0, } } #[must_use] - pub fn with_anon(mut self, mut inst: &[Instruction]) -> Intermediate { - self.num_result = 1; - - let code = self.new_forward(&mut inst); - let num_stack = self.data.last_stack; + pub fn build_anonymous(mut self, list: &[Instruction]) -> Intermediate { + let data = self.build_stat_list(list, 1); Intermediate { local_data: Vec::new(), num_param: 0, - num_stack, - code, + num_stack: data.num_stack, + code: Forward { body: data.code }, } } #[must_use] - pub fn with_index(mut self, index: usize, func: &'a FuncBody) -> Intermediate { - let arity = &self.type_info.arity_of(self.type_info.len_ex() + index); - - self.num_result = arity.num_result; - - let code = self.new_forward(&mut func.code().elements()); - let num_stack = self.data.last_stack; + pub fn build_indexed(mut self, index: usize, func: &FuncBody) -> Intermediate { + 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); Intermediate { local_data: func.locals().to_vec(), num_param: arity.num_param, - num_stack, - code, + num_stack: data.num_stack, + code: Forward { body: data.code }, } } - fn push_block_result(&mut self, typ: BlockType) { - let num = match typ { - BlockType::NoResult => { - return; - } + fn start_block(&mut self, typ: BlockType) { + let mut old = std::mem::take(&mut self.target); + + self.target.push_temporary(old.stack.len()); + self.target.num_result = match typ { + BlockType::NoResult => 0, BlockType::Value(_) => 1, BlockType::TypeIndex(i) => { - self.type_info - .raw_arity_of(i.try_into().unwrap()) - .num_result + let id = i.try_into().unwrap(); + + self.type_info.arity_of(id).num_result } }; - self.data.push_temporary(num); + old.leak_all(); + old.push_temporary(self.target.num_result); + + self.pending.push(old); } - fn gen_return(&mut self, stat: &mut Vec) { - let list = self.data.pop_many(self.num_result); + fn end_block(&mut self) { + let old = self.pending.pop().unwrap(); + let now = std::mem::replace(&mut self.target, old); - self.data.gen_leak_pending(stat); + self.target.num_stack = now.num_stack; - stat.push(Statement::Return(Return { list })); + match self.target.code.last_mut().unwrap() { + Statement::Forward(data) => data.body = now.code, + Statement::Backward(data) => data.body = now.code, + Statement::If(data) if now.is_else => data.falsey = now.code, + Statement::If(data) if !now.is_else => data.truthy = now.code, + _ => unreachable!(), + } } - fn gen_call(&mut self, func: usize, stat: &mut Vec) { - let arity = self.type_info.arity_of(func); - let param_list = self.data.pop_many(arity.num_param); + fn add_call(&mut self, func: usize) { + let arity = self.type_info.rel_arity_of(func); + let param_list = self.target.pop_len(arity.num_param); - let first = self.data.stack.len(); + let first = self.target.stack.len(); let result = first..first + arity.num_result; - self.data.push_temporary(arity.num_result); - self.data.gen_leak_pending(stat); + self.target.leak_all(); + self.target.push_temporary(arity.num_result); - stat.push(Statement::Call(Call { + let data = Statement::Call(Call { func, result, param_list, - })); + }); + + self.target.code.push(data); } - fn gen_call_indirect(&mut self, typ: usize, table: usize, stat: &mut Vec) { - let arity = self.type_info.raw_arity_of(typ); - let index = self.data.pop(); - let param_list = self.data.pop_many(arity.num_param); + fn add_call_indirect(&mut self, typ: usize, table: usize) { + let arity = self.type_info.arity_of(typ); + let index = self.target.pop_required(); + let param_list = self.target.pop_len(arity.num_param); - let first = self.data.stack.len(); + let first = self.target.stack.len(); let result = first..first + arity.num_result; - self.data.push_temporary(arity.num_result); - self.data.gen_leak_pending(stat); + self.target.leak_all(); + self.target.push_temporary(arity.num_result); - stat.push(Statement::CallIndirect(CallIndirect { + let data = Statement::CallIndirect(CallIndirect { table, index, result, param_list, - })); + }); + + self.target.code.push(data); } - fn drop_unreachable(list: &mut &[Instruction]) { + fn add_return(&mut self) { + let data = Statement::Return(Return { + list: self.target.pop_len(self.num_result), + }); + + self.target.leak_all(); + 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::End => { + self.nested_unreachable -= 1; + + if self.nested_unreachable == 0 { + self.end_block(); + } + } + _ => {} + } + } + + fn add_instruction(&mut self, inst: &Instruction) { use Instruction as Inst; - let mut level = 1; + if self.target.try_add_operation(inst) { + return; + } - loop { - let inst = &list[0]; - - *list = &list[1..]; - - match inst { - Inst::Block(_) | Inst::Loop(_) | Inst::If(_) => { - level += 1; - } - Inst::Else => { - if level == 1 { - break; - } - } - Inst::End => { - level -= 1; - - if level == 0 { - break; - } - } - _ => {} + match *inst { + Inst::Unreachable => { + self.nested_unreachable += 1; + self.target.code.push(Statement::Unreachable); } + Inst::Nop => {} + Inst::Block(typ) => { + let data = Statement::Forward(Forward { body: Vec::new() }); + + self.start_block(typ); + self.pending.last_mut().unwrap().code.push(data); + } + Inst::Loop(typ) => { + let data = Statement::Backward(Backward { body: Vec::new() }); + + self.start_block(typ); + self.pending.last_mut().unwrap().code.push(data); + } + Inst::If(typ) => { + let data = Statement::If(If { + cond: self.target.pop_required(), + truthy: Vec::new(), + falsey: Vec::new(), + }); + + self.start_block(typ); + self.pending.last_mut().unwrap().code.push(data); + } + Inst::Else => { + let num_result = self.target.num_result; + + self.target.leak_all(); + self.end_block(); + self.start_block(BlockType::NoResult); + + self.target.num_result = num_result; + self.target.is_else = true; + } + Inst::End => { + self.target.leak_all(); + self.end_block(); + } + Inst::Br(v) => { + self.nested_unreachable += 1; + + let data = Statement::Br(Br { + target: v.try_into().unwrap(), + }); + + self.target.leak_all(); + self.target.code.push(data); + } + Inst::BrIf(v) => { + let data = Statement::BrIf(BrIf { + cond: self.target.pop_required(), + target: v.try_into().unwrap(), + }); + + // FIXME: Does not push results unless true + // self.target.add_result_data(); + self.target.code.push(data); + } + Inst::BrTable(ref v) => { + self.nested_unreachable += 1; + + let data = Statement::BrTable(BrTable { + cond: self.target.pop_required(), + data: *v.clone(), + }); + + self.target.leak_all(); + self.target.code.push(data); + } + Inst::Return => { + self.nested_unreachable += 1; + self.add_return(); + } + 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 => { + let last = self.target.stack.len() - 1; + + if self.target.stack[last].has_side_effect() { + self.target.leak_at(last); + } + + self.target.pop_required(); + } + Inst::Select => { + let data = Expression::Select(Select { + cond: self.target.pop_required().into(), + b: self.target.pop_required().into(), + a: self.target.pop_required().into(), + }); + + self.target.push_tracked(data); + } + Inst::GetLocal(i) => { + let data = Expression::GetLocal(GetLocal { + var: i.try_into().unwrap(), + }); + + self.target.push_tracked(data); + } + Inst::SetLocal(i) => { + let var = i.try_into().unwrap(); + let data = Statement::SetLocal(SetLocal { + var, + value: self.target.pop_required(), + }); + + 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.pop_required(), + }); + + self.target.leak_local_write(var); + self.target.push_tracked(get); + self.target.code.push(set); + } + Inst::GetGlobal(i) => { + let data = Expression::GetGlobal(GetGlobal { + var: i.try_into().unwrap(), + }); + + self.target.push_tracked(data); + } + Inst::SetGlobal(i) => { + let var = i.try_into().unwrap(); + let data = Statement::SetGlobal(SetGlobal { + var, + value: self.target.pop_required(), + }); + + 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.leak_memory_write(memory); + self.target.push_tracked(data); + } + Inst::GrowMemory(i) => { + let memory = i.try_into().unwrap(); + let data = Expression::MemoryGrow(MemoryGrow { + memory, + value: self.target.pop_required().into(), + }); + + self.target.leak_memory_size(memory); + self.target.leak_memory_write(memory); + self.target.push_tracked(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!(), } } - #[allow(clippy::too_many_lines)] - fn new_stored_body(&mut self, list: &mut &[Instruction]) -> Vec { - use Instruction as Inst; + fn build_stat_list(&mut self, list: &[Instruction], num_result: usize) -> StatList { + self.nested_unreachable = 0; + self.num_result = num_result; - let mut stat = Vec::new(); - - self.data.save_pending(); - - loop { - let inst = &list[0]; - - *list = &list[1..]; - - if self.data.try_operation(inst) { - continue; - } - - match *inst { - Inst::Nop => {} - Inst::Unreachable => { - stat.push(Statement::Unreachable); - } - Inst::Block(t) => { - self.data.gen_leak_pending(&mut stat); - - let data = self.new_forward(list); - - self.push_block_result(t); - stat.push(Statement::Forward(data)); - } - Inst::Loop(t) => { - self.data.gen_leak_pending(&mut stat); - - let data = self.new_backward(list); - - self.push_block_result(t); - stat.push(Statement::Backward(data)); - } - Inst::If(t) => { - let cond = self.data.pop(); - - self.data.gen_leak_pending(&mut stat); - - let data = self.new_if(cond, list); - - self.push_block_result(t); - stat.push(Statement::If(data)); - } - Inst::Else => { - self.data.gen_leak_pending(&mut stat); - - break; - } - Inst::End => { - if list.is_empty() && self.num_result != 0 { - self.gen_return(&mut stat); - } else { - self.data.gen_leak_pending(&mut stat); - } - - break; - } - Inst::Br(target) => { - let target = target.try_into().unwrap(); - - self.data.gen_leak_pending(&mut stat); - stat.push(Statement::Br(Br { target })); - } - Inst::BrIf(target) => { - let target = target.try_into().unwrap(); - let cond = self.data.pop(); - - self.data.gen_leak_pending(&mut stat); - stat.push(Statement::BrIf(BrIf { cond, target })); - } - Inst::BrTable(ref t) => { - let cond = self.data.pop(); - - self.data.gen_leak_pending(&mut stat); - stat.push(Statement::BrTable(BrTable { - cond, - data: *t.clone(), - })); - } - Inst::Return => { - self.gen_return(&mut stat); - } - Inst::Call(i) => { - self.gen_call(i.try_into().unwrap(), &mut stat); - } - Inst::CallIndirect(i, t) => { - self.gen_call_indirect(i.try_into().unwrap(), t.into(), &mut stat); - } - Inst::Drop => { - self.data.pop(); - } - Inst::Select => { - let cond = Box::new(self.data.pop()); - let b = Box::new(self.data.pop()); - let a = Box::new(self.data.pop()); - - self.data.push(Expression::Select(Select { cond, a, b })); - } - Inst::GetLocal(var) => { - let var = var.try_into().unwrap(); - - self.data.push(Expression::GetLocal(GetLocal { var })); - } - Inst::SetLocal(var) => { - let var = var.try_into().unwrap(); - let value = self.data.pop(); - - self.data.gen_leak_pending(&mut stat); - stat.push(Statement::SetLocal(SetLocal { var, value })); - } - Inst::TeeLocal(var) => { - self.data.gen_leak_pending(&mut stat); - - let var = var.try_into().unwrap(); - let value = self.data.pop(); - - self.data.push(value.clone_temporary()); - stat.push(Statement::SetLocal(SetLocal { var, value })); - } - Inst::GetGlobal(var) => { - let var = var.try_into().unwrap(); - - self.data.push(Expression::GetGlobal(GetGlobal { var })); - } - Inst::SetGlobal(var) => { - let var = var.try_into().unwrap(); - let value = self.data.pop(); - - stat.push(Statement::SetGlobal(SetGlobal { var, value })); - } - Inst::I32Load(_, o) => self.data.push_load(LoadType::I32, o), - Inst::I64Load(_, o) => self.data.push_load(LoadType::I64, o), - Inst::F32Load(_, o) => self.data.push_load(LoadType::F32, o), - Inst::F64Load(_, o) => self.data.push_load(LoadType::F64, o), - Inst::I32Load8S(_, o) => self.data.push_load(LoadType::I32_I8, o), - Inst::I32Load8U(_, o) => self.data.push_load(LoadType::I32_U8, o), - Inst::I32Load16S(_, o) => self.data.push_load(LoadType::I32_I16, o), - Inst::I32Load16U(_, o) => self.data.push_load(LoadType::I32_U16, o), - Inst::I64Load8S(_, o) => self.data.push_load(LoadType::I64_I8, o), - Inst::I64Load8U(_, o) => self.data.push_load(LoadType::I64_U8, o), - Inst::I64Load16S(_, o) => self.data.push_load(LoadType::I64_I16, o), - Inst::I64Load16U(_, o) => self.data.push_load(LoadType::I64_U16, o), - Inst::I64Load32S(_, o) => self.data.push_load(LoadType::I64_I32, o), - Inst::I64Load32U(_, o) => self.data.push_load(LoadType::I64_U32, o), - Inst::I32Store(_, o) => self.data.gen_store(StoreType::I32, o, &mut stat), - Inst::I64Store(_, o) => self.data.gen_store(StoreType::I64, o, &mut stat), - Inst::F32Store(_, o) => self.data.gen_store(StoreType::F32, o, &mut stat), - Inst::F64Store(_, o) => self.data.gen_store(StoreType::F64, o, &mut stat), - Inst::I32Store8(_, o) => self.data.gen_store(StoreType::I32_N8, o, &mut stat), - Inst::I32Store16(_, o) => self.data.gen_store(StoreType::I32_N16, o, &mut stat), - Inst::I64Store8(_, o) => self.data.gen_store(StoreType::I64_N8, o, &mut stat), - Inst::I64Store16(_, o) => self.data.gen_store(StoreType::I64_N16, o, &mut stat), - Inst::I64Store32(_, o) => self.data.gen_store(StoreType::I64_N32, o, &mut stat), - Inst::CurrentMemory(memory) => { - let memory = memory.try_into().unwrap(); - - self.data - .push(Expression::MemorySize(MemorySize { memory })); - } - Inst::GrowMemory(memory) => { - let memory = memory.try_into().unwrap(); - let value = Box::new(self.data.pop()); - - // `MemoryGrow` is an expression *but* it has side effects - self.data - .push(Expression::MemoryGrow(MemoryGrow { memory, value })); - - self.data.gen_leak_pending(&mut stat); - } - Inst::I32Const(v) => self.data.push_constant(v), - Inst::I64Const(v) => self.data.push_constant(v), - Inst::F32Const(v) => self.data.push_constant(v), - Inst::F64Const(v) => self.data.push_constant(v), - _ => unreachable!(), - } - - if is_dead_precursor(inst) { - Self::drop_unreachable(list); - - break; + for inst in list.iter().take(list.len() - 1) { + if self.nested_unreachable == 0 { + self.add_instruction(inst); + } else { + self.drop_unreachable(inst); } } - self.data.load_pending(); - - stat - } - - fn new_if(&mut self, cond: Expression, list: &mut &[Instruction]) -> If { - let copied = <&[Instruction]>::clone(list); - let truthy = self.new_stored_body(list); - - let end = copied.len() - list.len() - 1; - let falsey = is_else_stat(&copied[end]) - .then(|| self.new_stored_body(list)) - .unwrap_or_default(); - - If { - cond, - truthy, - falsey, + if self.nested_unreachable == 0 && num_result != 0 { + self.add_return(); } - } - fn new_backward(&mut self, list: &mut &[Instruction]) -> Backward { - Backward { - body: self.new_stored_body(list), - } - } - - fn new_forward(&mut self, list: &mut &[Instruction]) -> Forward { - Forward { - body: self.new_stored_body(list), - } + std::mem::take(&mut self.target) } } diff --git a/wasm-ast/src/node.rs b/wasm-ast/src/node.rs index b057582..7b69e3f 100644 --- a/wasm-ast/src/node.rs +++ b/wasm-ast/src/node.rs @@ -629,19 +629,34 @@ pub enum Expression { impl Expression { #[must_use] - pub fn is_temporary(&self, wanted: usize) -> bool { - match self { - Expression::GetTemporary(v) => v.var == wanted, - _ => false, - } + pub fn has_side_effect(&self) -> bool { + matches!(self, Expression::MemorySize(_)) } #[must_use] - pub fn clone_temporary(&self) -> Self { - match self { - Expression::GetTemporary(v) => Expression::GetTemporary(v.clone()), - _ => unreachable!("not a temporary"), - } + pub fn is_temporary(&self, id: usize) -> bool { + matches!(self, Expression::GetTemporary(v) if v.var == id) + } + + #[must_use] + pub fn is_local_read(&self, id: usize) -> bool { + matches!(self, Expression::GetLocal(v) if v.var == id) + } + + #[must_use] + pub fn is_global_read(&self, id: usize) -> bool { + matches!(self, Expression::GetGlobal(v) if v.var == id) + } + + #[must_use] + pub fn is_memory_size(&self, id: usize) -> bool { + matches!(self, Expression::MemorySize(v) if v.memory == id) + } + + #[must_use] + pub fn is_memory_ref(&self, id: usize) -> bool { + matches!(self, Expression::MemoryGrow(v) if v.memory == id) + || (id == 0 && matches!(self, Expression::LoadAt(_))) } }