use wasmparser::BrTable;
use super::{BlockKind, Patch, PatchSlot};
use crate::error::InternalError;
use crate::module::compile::{fused_branch, wp_err, Translator};
use crate::module::op::{BrTableRange, BranchTarget, Op, Slot, NULLABLE_BIT};
use crate::Result;
pub(super) fn fixup(keep: u32, pop: u32) -> Result<(u16, u16)> {
let conv = |v: u32| {
u16::try_from(v).map_err(|_| crate::Error::msg("branch stack fixup exceeds 65535 operands"))
};
Ok((conv(keep)?, conv(pop)?))
}
impl Translator<'_> {
pub(super) fn branch_operand(&mut self, operand: Slot, keep: u16) -> Slot {
if keep == 0 {
return operand;
}
self.materialize(u32::from(keep));
let top = self.height_slot(0);
if operand != top {
self.emit(Op::Copy(top, operand));
}
top
}
pub(in crate::module::compile) fn br(&mut self, depth: u32) -> Result<()> {
let (target, patch_frame) = self.branch_target(depth)?;
self.materialize(u32::from(target.keep));
if patch_frame.is_none() && target.keep == 0 {
if let Some(rotated) = self.rotated_header(target.ip) {
self.emit(rotated);
}
}
let top = self.height_slot(0);
let idx = self.next_ip();
self.emit(Op::Br(target, top));
self.register_branch(patch_frame, idx, PatchSlot::Single);
self.reachable = false;
Ok(())
}
fn rotated_header(&self, header: u32) -> Option<Op> {
let body = header.checked_add(1)?;
let stay = BranchTarget {
ip: body,
keep: 0,
pop: 0,
};
Some(match *self.op_get(header)? {
Op::BrIfCmp {
kind,
negate,
lhs,
rhs,
..
} => Op::BrIfCmp {
kind,
negate: !negate,
ip: body,
lhs,
rhs,
},
Op::BrIfCmpImm {
kind,
negate,
lhs,
imm,
..
} => Op::BrIfCmpImm {
kind,
negate: !negate,
ip: body,
lhs,
imm,
},
Op::BrIf(t, s) if t.keep == 0 => Op::BrIfNot(stay, s),
Op::BrIfNot(t, s) if t.keep == 0 => Op::BrIf(stay, s),
_ => return None,
})
}
pub(in crate::module::compile) fn br_if(&mut self, depth: u32) -> Result<()> {
let cond = self.operand(0);
self.pop(1);
let (target, patch_frame) = self.branch_target(depth)?;
let s = self.branch_operand(cond, target.keep);
let fused = self.fusable_cmp.take();
if let Some((kind, lhs, rhs, _)) = fused.filter(|f| f.3 == cond && target.keep == 0) {
let idx = self.next_ip() - 1;
*self.op_mut(idx) = fused_branch(kind, false, target, lhs, rhs);
self.clear_fusion(); self.register_branch(patch_frame, idx, PatchSlot::Single);
return Ok(());
}
let idx = self.next_ip();
self.emit(Op::BrIf(target, s));
self.register_branch(patch_frame, idx, PatchSlot::Single);
Ok(())
}
pub(in crate::module::compile) fn br_table(&mut self, table: &BrTable<'_>) -> Result<()> {
let index = self.operand(0);
self.pop(1);
let base = self.code.br_tables.len() as u32 - self.base.br_tables;
let mut patches = std::mem::take(&mut self.table_patches);
patches.clear();
let mut len = 0u32;
for (k, depth) in table.targets().enumerate() {
let (bt, frame) = self.branch_target(depth.map_err(wp_err)?)?;
self.push_edge(bt);
if let Some(i) = frame {
patches.push((i, PatchSlot::TableCase(k as u32)));
}
len += 1;
}
let (default, default_frame) = self.branch_target(table.default())?;
self.push_edge(default);
if let Some(i) = default_frame {
patches.push((i, PatchSlot::TableDefault));
}
let s = self.branch_operand(index, default.keep);
let idx = self.next_ip();
self.emit(Op::BrTable(BrTableRange { base, len }, s));
for &(i, slot) in &patches {
let frame = &mut self.ctrl[i];
frame.end_patches.push(Patch { op: idx, slot });
frame.end_targeted = true;
}
self.table_patches = patches;
self.reachable = false;
Ok(())
}
pub(in crate::module::compile) fn ret(&mut self) -> Result<()> {
let arity = self.ctrl[0].result_count;
let pop = self
.height
.checked_sub(arity)
.ok_or(InternalError::Compiler("return operand height underflow"))?;
let (keep, pop) = fixup(arity, pop)?;
self.materialize(arity);
let top = self.height_slot(0);
let idx = self.next_ip();
self.emit(Op::Br(BranchTarget { ip: 0, keep, pop }, top));
self.ctrl[0].end_patches.push(Patch {
op: idx,
slot: PatchSlot::Single,
});
self.ctrl[0].end_targeted = true;
self.reachable = false;
Ok(())
}
pub(super) fn branch_target(&self, depth: u32) -> Result<(BranchTarget, Option<usize>)> {
let i = self
.ctrl
.len()
.checked_sub(1)
.and_then(|top| top.checked_sub(depth as usize))
.ok_or(InternalError::Compiler("branch control depth out of range"))?;
let frame = self
.ctrl
.get(i)
.ok_or(InternalError::Compiler("branch control frame missing"))?;
let arity = frame.label_arity();
let target_height = frame
.base_height
.checked_add(arity)
.ok_or(InternalError::Compiler("branch target height overflow"))?;
let pop = self
.height
.checked_sub(target_height)
.ok_or(InternalError::Compiler("branch operand height underflow"))?;
let (keep, pop) = fixup(arity, pop)?;
Ok(if frame.kind == BlockKind::Loop {
(
BranchTarget {
ip: frame.start_ip,
keep,
pop,
},
None,
)
} else {
(BranchTarget { ip: 0, keep, pop }, Some(i))
})
}
pub(super) fn register_branch(&mut self, frame: Option<usize>, op: u32, slot: PatchSlot) {
if let Some(i) = frame {
let f = &mut self.ctrl[i];
f.end_patches.push(Patch { op, slot });
f.end_targeted = true;
}
}
pub(super) fn patch_ip(&mut self, op: u32, slot: PatchSlot, ip: u32) {
if let Op::BrTable(range, _) = self.op_ref(op) {
let range = *range;
let flat = match slot {
PatchSlot::TableCase(k) => range.base + k,
PatchSlot::TableDefault => range.base + range.len,
PatchSlot::Single => unreachable!("single slot on br_table"),
};
self.edge_mut(flat).ip = ip;
return;
}
if let Op::BrOnCast { target, .. } | Op::BrOnCastFail { target, .. } = self.op_ref(op) {
let flat = target & !NULLABLE_BIT;
self.edge_mut(flat).ip = ip;
return;
}
match self.op_mut(op) {
Op::Br(t, _)
| Op::BrIf(t, _)
| Op::BrIfNot(t, _)
| Op::BrOnNull(t, _)
| Op::BrOnNonNull(t, _) => {
t.ip = ip;
}
Op::BrIfCmp { ip: target_ip, .. } | Op::BrIfCmpImm { ip: target_ip, .. } => {
*target_ip = ip;
}
_ => unreachable!("patch on non-branch op"),
}
}
}