Add new Builder
backend
This commit is contained in:
parent
863319d1bc
commit
472f37d059
@ -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<Intermediate> {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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<Intermediate> {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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<usize> {
|
||||
@ -103,126 +111,144 @@ impl<'a> TypeInfo<'a> {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Stacked {
|
||||
pending_list: Vec<Vec<Expression>>,
|
||||
struct StatList {
|
||||
code: Vec<Statement>,
|
||||
stack: Vec<Expression>,
|
||||
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<Statement>) {
|
||||
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<Expression> {
|
||||
fn pop_len(&mut self, len: usize) -> Vec<Expression> {
|
||||
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<T: Into<Value>>(&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<P>(&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<Statement>) {
|
||||
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<T: Into<Value>>(&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<StatList>,
|
||||
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<Statement>) {
|
||||
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<Statement>) {
|
||||
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<Statement>) {
|
||||
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<Statement> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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(_)))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user