use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::entity::{AggId, Block, FuncId, GlobalId, Inst, NONE, StackSlot, Value, ValueRange};
use crate::error::{BuildError, ValidationError, ValidationErrorKind as K};
use crate::function::{Function, MAX_ENTITIES, SlotData, ValueDef};
use crate::inst::{BlockCall, Call, InstData, MemFlags, MemOrder, RmwOp, SwitchCase, Terminator};
use crate::module::Decls;
use crate::ops::{BinaryOp, CmpOp, ConvOp, Overflow, Policy, UnaryOp};
use crate::types::{CallConv, MemType, Signature, Type};
use crate::typing;
static EMPTY_SIG: Signature = Signature {
params: Vec::new(),
returns: Vec::new(),
conv: CallConv::Fast,
};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum Position {
End(Block),
Before(Inst),
After(Inst),
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct Remap {
values: Vec<u32>,
blocks: Vec<u32>,
insts: Vec<u32>,
}
impl Remap {
#[must_use]
pub fn value(&self, old: Value) -> Option<Value> {
self.values
.get(old.index())
.filter(|&&v| v != NONE)
.map(|&v| Value::from_u32(v))
}
#[must_use]
pub fn block(&self, old: Block) -> Option<Block> {
self.blocks
.get(old.index())
.filter(|&&v| v != NONE)
.map(|&v| Block::from_u32(v))
}
#[must_use]
pub fn inst(&self, old: Inst) -> Option<Inst> {
self.insts
.get(old.index())
.filter(|&&v| v != NONE)
.map(|&v| Inst::from_u32(v))
}
}
#[derive(Debug)]
pub struct Builder<'m> {
decls: &'m Decls,
func: &'m mut Function,
id: FuncId,
pos: Position,
scratch: Vec<Type>,
}
impl<'m> Builder<'m> {
pub(crate) fn new(decls: &'m Decls, func: &'m mut Function, id: FuncId) -> Builder<'m> {
let entry = func.entry();
Builder {
decls,
func,
id,
pos: Position::End(entry),
scratch: Vec::new(),
}
}
#[must_use]
pub fn func(&self) -> &Function {
self.func
}
#[must_use]
pub fn id(&self) -> FuncId {
self.id
}
#[must_use]
pub fn signature(&self) -> &Signature {
self.decls.func_sig(self.id).unwrap_or(&EMPTY_SIG)
}
pub fn param(&self, index: usize) -> Result<Value, BuildError> {
self.func
.block_params(self.func.entry())
.get(index)
.copied()
.ok_or(BuildError::UnknownParam { index })
}
pub fn block_param(&self, block: Block, index: usize) -> Result<Value, BuildError> {
self.func
.block_params(block)
.get(index)
.copied()
.ok_or(BuildError::UnknownBlockParam { block, index })
}
#[must_use]
pub fn results(&self, inst: Inst) -> ValueRange {
self.func.results(inst)
}
#[must_use]
pub fn position(&self) -> Position {
self.pos
}
#[must_use]
pub fn current_block(&self) -> Option<Block> {
match self.pos {
Position::End(b) => self.func.is_live_block(b).then_some(b),
Position::Before(i) | Position::After(i) => self.func.inst_block(i),
}
}
pub fn validate(&self) -> Result<(), ValidationError> {
crate::validate::validate_body(self.decls, self.id, self.func)
}
pub fn set_position(&mut self, pos: Position) -> Result<(), BuildError> {
match pos {
Position::End(b) => {
if !self.func.is_live_block(b) {
return Err(BuildError::UnknownBlock { block: b });
}
}
Position::Before(i) | Position::After(i) => {
if self.func.inst_block(i).is_none() {
return Err(BuildError::UnknownInst { inst: i });
}
}
}
self.pos = pos;
Ok(())
}
pub fn switch_to(&mut self, block: Block) -> Result<(), BuildError> {
self.set_position(Position::End(block))
}
fn current(&self) -> Result<Block, BuildError> {
self.current_block().ok_or(match self.pos {
Position::End(block) => BuildError::UnknownBlock { block },
Position::Before(inst) | Position::After(inst) => BuildError::UnknownInst { inst },
})
}
pub fn create_block(&mut self, params: &[Type]) -> Result<Block, BuildError> {
if self.func.values.len() + params.len() > MAX_ENTITIES as usize {
return Err(BuildError::CapacityExceeded);
}
let block = self.func.push_block().ok_or(BuildError::CapacityExceeded)?;
for &ty in params {
let _ = self.add_block_param(block, ty)?;
}
Ok(block)
}
pub fn add_block_param(&mut self, block: Block, ty: Type) -> Result<Value, BuildError> {
if !self.func.is_live_block(block) {
return Err(BuildError::UnknownBlock { block });
}
let index = self.func.block_params(block).len() as u32;
let v = self
.func
.push_value(ty, ValueDef::Param { block, index })
.ok_or(BuildError::CapacityExceeded)?;
if let Some(node) = self.func.blocks.get_mut(block.index()) {
node.params.push(v);
}
Ok(v)
}
pub fn remove_block_param(&mut self, block: Block, index: usize) -> Result<(), BuildError> {
if !self.func.is_live_block(block) {
return Err(BuildError::UnknownBlock { block });
}
let params = &mut self.func.blocks[block.index()].params;
if index >= params.len() {
return Err(BuildError::UnknownBlockParam { block, index });
}
let removed = params.remove(index);
let rest: Vec<Value> = params[index..].to_vec();
if let Some(node) = self.func.values.get_mut(removed.index()) {
node.def = ValueDef::Detached;
}
for (k, v) in rest.into_iter().enumerate() {
if let Some(node) = self.func.values.get_mut(v.index()) {
node.def = ValueDef::Param {
block,
index: (index + k) as u32,
};
}
}
for node in self.func.blocks.iter_mut().filter(|b| b.live) {
if let Some(term) = &mut node.term {
term.for_each_successor_mut(|dest| {
if dest.block == block && index < dest.args.len() {
let _ = dest.args.remove(index);
}
});
}
}
Ok(())
}
pub fn remove_block(&mut self, block: Block) -> Result<(), BuildError> {
if block == self.func.entry() {
return Err(BuildError::EntryBlock);
}
if !self.func.is_live_block(block) {
return Err(BuildError::UnknownBlock { block });
}
let insts: Vec<Inst> = self.func.insts(block).collect();
for inst in insts {
self.detach_inst(inst);
}
let node = &mut self.func.blocks[block.index()];
node.live = false;
node.term = None;
let params = core::mem::take(&mut node.params);
for p in params {
if let Some(v) = self.func.values.get_mut(p.index()) {
v.def = ValueDef::Detached;
}
}
if self.current_block().is_none() {
self.pos = Position::End(self.func.entry());
}
Ok(())
}
pub fn split_block(&mut self, at: Inst) -> Result<Block, BuildError> {
let block = self
.func
.inst_block(at)
.ok_or(BuildError::UnknownInst { inst: at })?;
let new = self.func.push_block().ok_or(BuildError::CapacityExceeded)?;
let mut moving = Vec::new();
let mut cur = Some(at);
while let Some(i) = cur {
moving.push(i);
cur = self.func.next_inst(i);
}
for i in moving {
self.func.unlink(i);
self.func.link_at_end(i, new);
}
let term = self.func.blocks[block.index()].term.take();
self.func.blocks[new.index()].term = term;
self.func.blocks[block.index()].term = Some(Terminator::Jump(BlockCall::new(new, &[])));
Ok(new)
}
pub fn insert(&mut self, data: InstData) -> Result<Inst, BuildError> {
self.scratch.clear();
typing::inst_types(self.decls, self.func, &data, false, &mut self.scratch)?;
let block = self.current()?;
if self.func.insts.len() >= MAX_ENTITIES as usize
|| self.func.values.len() + self.scratch.len() > MAX_ENTITIES as usize
{
return Err(BuildError::CapacityExceeded);
}
let inst = self
.func
.push_inst(data)
.ok_or(BuildError::CapacityExceeded)?;
let start = self.func.values.len() as u32;
for (i, &ty) in self.scratch.iter().enumerate() {
let _ = self.func.push_value(
ty,
ValueDef::Result {
inst,
index: i as u32,
},
);
}
self.func.insts[inst.index()].results = ValueRange {
start,
len: self.scratch.len() as u32,
};
match self.pos {
Position::End(_) => self.func.link_at_end(inst, block),
Position::Before(at) => self.func.link_before(inst, at),
Position::After(at) => {
self.func.link_after(inst, at);
self.pos = Position::After(inst);
}
}
Ok(inst)
}
fn insert1(&mut self, data: InstData) -> Result<Value, BuildError> {
let inst = self.insert(data)?;
self.func
.results(inst)
.first()
.ok_or(BuildError::Invalid(K::ResultMismatch))
}
pub fn iconst(&mut self, ty: Type, value: i128) -> Result<Value, BuildError> {
let bad = BuildError::Invalid(K::BadConst {
ty,
bits: value as u64,
});
let Some(width) = ty.int_bits() else {
return Err(bad);
};
let (lo, hi) = if ty.is_signed_int() {
(-(1i128 << (width - 1)), (1i128 << (width - 1)) - 1)
} else {
(0, (1i128 << width) - 1)
};
if value < lo || value > hi {
return Err(bad);
}
let mask: u128 = if width == 64 {
u64::MAX as u128
} else {
(1u128 << width) - 1
};
let bits = ((value as u128) & mask) as u64;
self.insert1(InstData::Const { ty, bits })
}
pub fn f64const(&mut self, value: f64) -> Result<Value, BuildError> {
self.insert1(InstData::Const {
ty: Type::F64,
bits: value.to_bits(),
})
}
pub fn f32const(&mut self, value: f32) -> Result<Value, BuildError> {
self.insert1(InstData::Const {
ty: Type::F32,
bits: u64::from(value.to_bits()),
})
}
pub fn bconst(&mut self, value: bool) -> Result<Value, BuildError> {
self.insert1(InstData::Const {
ty: Type::Bool,
bits: u64::from(value),
})
}
pub fn null(&mut self, ty: Type) -> Result<Value, BuildError> {
if !matches!(ty, Type::Ptr | Type::Ref) {
return Err(BuildError::Invalid(K::BadConst { ty, bits: 0 }));
}
self.insert1(InstData::Const { ty, bits: 0 })
}
pub fn unary(&mut self, op: UnaryOp, policy: Policy, arg: Value) -> Result<Value, BuildError> {
self.insert1(InstData::Unary { op, policy, arg })
}
pub fn binary(
&mut self,
op: BinaryOp,
policy: Policy,
lhs: Value,
rhs: Value,
) -> Result<Value, BuildError> {
self.insert1(InstData::Binary {
op,
policy,
args: [lhs, rhs],
})
}
fn arith(
&mut self,
op: BinaryOp,
lhs: Value,
rhs: Value,
overflow: Overflow,
) -> Result<Value, BuildError> {
let ty = typing::value_ty(self.func, lhs)?;
let policy = if ty.is_int() {
Policy::overflow(overflow)
} else {
Policy::NONE
};
self.binary(op, policy, lhs, rhs)
}
pub fn add(&mut self, lhs: Value, rhs: Value, overflow: Overflow) -> Result<Value, BuildError> {
self.arith(BinaryOp::Add, lhs, rhs, overflow)
}
pub fn sub(&mut self, lhs: Value, rhs: Value, overflow: Overflow) -> Result<Value, BuildError> {
self.arith(BinaryOp::Sub, lhs, rhs, overflow)
}
pub fn mul(&mut self, lhs: Value, rhs: Value, overflow: Overflow) -> Result<Value, BuildError> {
self.arith(BinaryOp::Mul, lhs, rhs, overflow)
}
pub fn compare(&mut self, op: CmpOp, lhs: Value, rhs: Value) -> Result<Value, BuildError> {
self.insert1(InstData::Compare {
op,
args: [lhs, rhs],
})
}
pub fn fma(&mut self, a: Value, b: Value, c: Value) -> Result<Value, BuildError> {
self.insert1(InstData::Fma { args: [a, b, c] })
}
pub fn select(
&mut self,
cond: Value,
if_true: Value,
if_false: Value,
) -> Result<Value, BuildError> {
self.insert1(InstData::Select {
cond,
args: [if_true, if_false],
})
}
pub fn convert(
&mut self,
op: ConvOp,
policy: Policy,
arg: Value,
to: Type,
) -> Result<Value, BuildError> {
self.insert1(InstData::Convert {
op,
policy,
to,
arg,
})
}
pub fn stack_slot(&mut self, size: u32, align: u32) -> Result<StackSlot, BuildError> {
if !crate::module::valid_align(u64::from(align), u64::from(SlotData::MAX_ALIGN)) {
return Err(BuildError::Invalid(K::BadAlignment {
align: u64::from(align),
}));
}
let index = u32::try_from(self.func.slots.len())
.ok()
.filter(|&i| i < MAX_ENTITIES)
.ok_or(BuildError::CapacityExceeded)?;
self.func.slots.push(SlotData { size, align });
Ok(StackSlot::from_u32(index))
}
pub fn stack_slot_for(&mut self, ty: MemType) -> Result<StackSlot, BuildError> {
let layout = self.decls.layout(ty).ok_or(match ty {
MemType::Agg(agg) => BuildError::Invalid(K::UnknownAggregate { agg }),
MemType::Scalar(_) => BuildError::Invalid(K::ResultMismatch),
})?;
let size = u32::try_from(layout.size()).map_err(|_| match ty {
MemType::Agg(agg) => BuildError::Invalid(K::BadAggregate { agg }),
MemType::Scalar(_) => BuildError::CapacityExceeded,
})?;
self.stack_slot(size, layout.align())
}
pub fn stack_addr(&mut self, slot: StackSlot) -> Result<Value, BuildError> {
self.insert1(InstData::StackAddr { slot })
}
pub fn global_addr(&mut self, global: GlobalId) -> Result<Value, BuildError> {
self.insert1(InstData::GlobalAddr { global })
}
pub fn func_addr(&mut self, func: FuncId) -> Result<Value, BuildError> {
self.insert1(InstData::FuncAddr { func })
}
pub fn field_addr(&mut self, base: Value, agg: AggId, field: u32) -> Result<Value, BuildError> {
self.insert1(InstData::FieldAddr { base, agg, field })
}
pub fn elem_addr(
&mut self,
base: Value,
elem: MemType,
index: Value,
) -> Result<Value, BuildError> {
self.insert1(InstData::ElemAddr { base, elem, index })
}
pub fn ptr_offset(&mut self, base: Value, offset: Value) -> Result<Value, BuildError> {
self.insert1(InstData::PtrOffset { base, offset })
}
pub fn load(&mut self, ty: Type, addr: Value, flags: MemFlags) -> Result<Value, BuildError> {
self.insert1(InstData::Load { ty, addr, flags })
}
pub fn store(
&mut self,
value: Value,
addr: Value,
flags: MemFlags,
) -> Result<Inst, BuildError> {
self.insert(InstData::Store { value, addr, flags })
}
pub fn atomic_load(
&mut self,
ty: Type,
addr: Value,
order: MemOrder,
) -> Result<Value, BuildError> {
self.insert1(InstData::AtomicLoad { ty, addr, order })
}
pub fn atomic_store(
&mut self,
value: Value,
addr: Value,
order: MemOrder,
) -> Result<Inst, BuildError> {
self.insert(InstData::AtomicStore { value, addr, order })
}
pub fn atomic_rmw(
&mut self,
op: RmwOp,
addr: Value,
value: Value,
order: MemOrder,
) -> Result<Value, BuildError> {
self.insert1(InstData::AtomicRmw {
op,
addr,
value,
order,
})
}
pub fn cmpxchg(
&mut self,
addr: Value,
expected: Value,
new: Value,
success: MemOrder,
failure: MemOrder,
) -> Result<Inst, BuildError> {
self.insert(InstData::CmpXchg {
addr,
expected,
new,
success,
failure,
})
}
pub fn fence(&mut self, order: MemOrder) -> Result<Inst, BuildError> {
self.insert(InstData::Fence { order })
}
pub fn memcpy(
&mut self,
dst: Value,
src: Value,
len: Value,
flags: MemFlags,
) -> Result<Inst, BuildError> {
self.insert(InstData::MemCopy {
dst,
src,
len,
flags,
})
}
pub fn memmove(
&mut self,
dst: Value,
src: Value,
len: Value,
flags: MemFlags,
) -> Result<Inst, BuildError> {
self.insert(InstData::MemMove {
dst,
src,
len,
flags,
})
}
pub fn memset(
&mut self,
dst: Value,
byte: Value,
len: Value,
flags: MemFlags,
) -> Result<Inst, BuildError> {
self.insert(InstData::MemSet {
dst,
byte,
len,
flags,
})
}
pub fn call(&mut self, call: Call) -> Result<Inst, BuildError> {
self.insert(InstData::Call(Box::new(call)))
}
pub fn safepoint(&mut self, gc: &[Value]) -> Result<Inst, BuildError> {
self.insert(InstData::Safepoint { gc: gc.into() })
}
pub fn set_terminator(&mut self, term: Terminator) -> Result<(), BuildError> {
let block = self.current()?;
if self.func.terminator(block).is_some() {
return Err(BuildError::AlreadyTerminated { block });
}
let sig = self.decls.func_sig(self.id).unwrap_or(&EMPTY_SIG);
typing::check_terminator(self.decls, self.func, sig, &term, &mut self.scratch)?;
self.func.blocks[block.index()].term = Some(term);
Ok(())
}
pub fn remove_terminator(&mut self, block: Block) -> Result<Option<Terminator>, BuildError> {
if !self.func.is_live_block(block) {
return Err(BuildError::UnknownBlock { block });
}
Ok(self.func.blocks[block.index()].term.take())
}
pub fn jump(&mut self, block: Block, args: &[Value]) -> Result<(), BuildError> {
self.set_terminator(Terminator::Jump(BlockCall::new(block, args)))
}
pub fn branch(
&mut self,
cond: Value,
then_block: Block,
then_args: &[Value],
else_block: Block,
else_args: &[Value],
) -> Result<(), BuildError> {
self.set_terminator(Terminator::Branch {
cond,
then_dest: BlockCall::new(then_block, then_args),
else_dest: BlockCall::new(else_block, else_args),
})
}
pub fn switch(
&mut self,
value: Value,
cases: Vec<SwitchCase>,
default: BlockCall,
) -> Result<(), BuildError> {
self.set_terminator(Terminator::Switch {
value,
cases,
default,
})
}
pub fn ret(&mut self, values: &[Value]) -> Result<(), BuildError> {
self.set_terminator(Terminator::Return {
values: values.to_vec(),
})
}
pub fn unreachable(&mut self) -> Result<(), BuildError> {
self.set_terminator(Terminator::Unreachable)
}
pub fn trap(&mut self, code: u32) -> Result<(), BuildError> {
self.set_terminator(Terminator::Trap { code })
}
pub fn tail_call(&mut self, call: Call) -> Result<(), BuildError> {
self.set_terminator(Terminator::TailCall(Box::new(call)))
}
pub fn invoke(
&mut self,
call: Call,
normal: BlockCall,
unwind: BlockCall,
) -> Result<(), BuildError> {
self.set_terminator(Terminator::Invoke {
call: Box::new(call),
normal,
unwind,
})
}
pub fn check(
&mut self,
op: InstData,
normal: BlockCall,
error: BlockCall,
) -> Result<(), BuildError> {
self.set_terminator(Terminator::Check {
op: Box::new(op),
normal,
error,
})
}
pub fn resume(&mut self, payload: Value) -> Result<(), BuildError> {
self.set_terminator(Terminator::Resume { payload })
}
pub fn replace_all_uses(&mut self, old: Value, new: Value) -> Result<(), BuildError> {
if old.index() >= self.func.values.len() {
return Err(BuildError::UnknownValue { value: old });
}
if new.index() >= self.func.values.len() {
return Err(BuildError::UnknownValue { value: new });
}
let (r_old, r_new) = (self.func.resolve(old), self.func.resolve(new));
if r_old == r_new {
return Ok(());
}
let (t_old, t_new) = (
self.func.values[r_old.index()].ty,
self.func.values[r_new.index()].ty,
);
if t_old != t_new {
return Err(BuildError::Invalid(K::TypeMismatch {
value: Some(new),
expected: t_old,
found: t_new,
}));
}
self.func.values[old.index()].alias = r_new.as_u32();
Ok(())
}
fn detach_inst(&mut self, inst: Inst) {
self.func.unlink(inst);
let results = self.func.results(inst);
for v in results {
if let Some(node) = self.func.values.get_mut(v.index()) {
node.def = ValueDef::Detached;
}
}
}
pub fn remove_inst(&mut self, inst: Inst) -> Result<(), BuildError> {
let block = self
.func
.inst_block(inst)
.ok_or(BuildError::UnknownInst { inst })?;
let (prev, next) = (self.func.prev_inst(inst), self.func.next_inst(inst));
self.detach_inst(inst);
self.pos = match self.pos {
Position::Before(i) if i == inst => next.map_or(Position::End(block), Position::Before),
Position::After(i) if i == inst => match (prev, next) {
(Some(p), _) => Position::After(p),
(None, Some(n)) => Position::Before(n),
(None, None) => Position::End(block),
},
other => other,
};
Ok(())
}
pub fn replace_inst(&mut self, inst: Inst, data: InstData) -> Result<(), BuildError> {
if self.func.inst_block(inst).is_none() {
return Err(BuildError::UnknownInst { inst });
}
self.scratch.clear();
typing::inst_types(self.decls, self.func, &data, false, &mut self.scratch)?;
let results = self.func.results(inst);
let same = results.len() == self.scratch.len()
&& results
.iter()
.zip(self.scratch.iter())
.all(|(v, &t)| self.func.value_type(v) == Some(t));
if !same {
return Err(BuildError::Invalid(K::ResultMismatch));
}
self.func.insts[inst.index()].data = data;
Ok(())
}
pub fn resolve_aliases(&mut self) {
let has_alias = self.func.values.iter().any(|v| v.alias != NONE);
if !has_alias {
return;
}
let snapshot: Vec<u32> = (0..self.func.values.len() as u32)
.map(|v| self.func.resolve(Value::from_u32(v)).as_u32())
.collect();
let resolve =
|v: Value| Value::from_u32(snapshot.get(v.index()).copied().unwrap_or(v.as_u32()));
for node in self.func.insts.iter_mut().filter(|n| n.block != NONE) {
node.data.map_operands(resolve);
}
for node in self.func.blocks.iter_mut().filter(|b| b.live) {
if let Some(term) = &mut node.term {
term.map_operands(resolve);
}
}
}
pub fn compact(&mut self) -> Remap {
self.resolve_aliases();
let remap = crate::compact::compact(self.func);
self.pos = Position::End(self.func.entry());
remap
}
pub(crate) fn remap_from_parts(values: Vec<u32>, blocks: Vec<u32>, insts: Vec<u32>) -> Remap {
Remap {
values,
blocks,
insts,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::module::{Linkage, Module};
fn module_with(params: &[Type], returns: &[Type]) -> (Module, FuncId) {
let mut m = Module::new("t");
let f = m
.declare_function("f", &Signature::new(params, returns), Linkage::Export)
.unwrap_or(FuncId::from_u32(0));
(m, f)
}
#[test]
fn test_terminator_is_never_overwritten() {
let (mut m, f) = module_with(&[], &[]);
let Ok(mut b) = m.build(f) else { return };
assert_eq!(b.ret(&[]), Ok(()));
let entry = b.func().entry();
assert_eq!(
b.ret(&[]),
Err(BuildError::AlreadyTerminated { block: entry })
);
assert_eq!(
b.remove_terminator(entry),
Ok(Some(Terminator::Return { values: Vec::new() }))
);
assert_eq!(b.unreachable(), Ok(()));
}
#[test]
fn test_refused_instruction_changes_nothing() {
let (mut m, f) = module_with(&[Type::I32, Type::I64], &[]);
let Ok(mut b) = m.build(f) else { return };
let (x, y) = (
b.param(0).unwrap_or(Value::from_u32(0)),
b.param(1).unwrap_or(Value::from_u32(0)),
);
let before = (b.func().value_count(), b.func().inst_count());
assert!(b.add(x, y, Overflow::Wrap).is_err());
assert!(b.add(x, x, Overflow::Error).is_err());
assert_eq!((b.func().value_count(), b.func().inst_count()), before);
}
#[test]
fn test_position_after_keeps_insertion_order() {
let (mut m, f) = module_with(&[], &[]);
let Ok(mut b) = m.build(f) else { return };
let a = b.iconst(Type::I32, 1).unwrap_or(Value::from_u32(0));
let last = b.iconst(Type::I32, 9).unwrap_or(Value::from_u32(0));
let ai = b.func().defining_inst(a).unwrap_or(Inst::from_u32(0));
assert!(b.set_position(Position::After(ai)).is_ok());
let x = b.iconst(Type::I32, 2).unwrap_or(Value::from_u32(0));
let y = b.iconst(Type::I32, 3).unwrap_or(Value::from_u32(0));
let entry = b.func().entry();
let order: Vec<Value> = b
.func()
.insts(entry)
.filter_map(|i| b.func().results(i).first())
.collect();
assert_eq!(order, [a, x, y, last]);
}
#[test]
fn test_remove_inst_moves_position_off_it() {
let (mut m, f) = module_with(&[], &[]);
let Ok(mut b) = m.build(f) else { return };
let a = b.iconst(Type::I32, 1).unwrap_or(Value::from_u32(0));
let ai = b.func().defining_inst(a).unwrap_or(Inst::from_u32(0));
assert!(b.set_position(Position::After(ai)).is_ok());
assert!(b.remove_inst(ai).is_ok());
assert_eq!(b.position(), Position::End(b.func().entry()));
assert!(b.iconst(Type::I32, 2).is_ok());
assert_eq!(b.remove_inst(ai), Err(BuildError::UnknownInst { inst: ai }));
}
#[test]
fn test_replace_all_uses_rejects_type_change_and_is_idempotent() {
let (mut m, f) = module_with(&[Type::I32, Type::I64, Type::I32], &[]);
let Ok(mut b) = m.build(f) else { return };
let (x, y, z) = (
b.param(0).unwrap_or(Value::from_u32(0)),
b.param(1).unwrap_or(Value::from_u32(0)),
b.param(2).unwrap_or(Value::from_u32(0)),
);
assert!(b.replace_all_uses(x, y).is_err());
assert!(b.replace_all_uses(x, z).is_ok());
assert!(b.replace_all_uses(z, x).is_ok()); assert!(b.replace_all_uses(x, x).is_ok());
assert_eq!(b.func().resolve(x), z);
assert_eq!(b.func().resolve(z), z);
assert_eq!(
b.replace_all_uses(Value::from_u32(99), x),
Err(BuildError::UnknownValue {
value: Value::from_u32(99)
})
);
}
#[test]
fn test_remove_block_param_fixes_edges() {
let (mut m, f) = module_with(&[Type::I32], &[]);
let Ok(mut b) = m.build(f) else { return };
let x = b.param(0).unwrap_or(Value::from_u32(0));
let next = b
.create_block(&[Type::I32, Type::I32])
.unwrap_or(Block::from_u32(0));
assert!(b.jump(next, &[x, x]).is_ok());
assert!(b.switch_to(next).is_ok());
assert!(b.ret(&[]).is_ok());
assert!(b.remove_block_param(next, 0).is_ok());
assert!(b.validate().is_ok());
let p = b.block_param(next, 0).unwrap_or(Value::from_u32(0));
assert_eq!(
b.func().value_def(p),
Some(ValueDef::Param {
block: next,
index: 0
})
);
assert!(b.remove_block_param(next, 3).is_err());
}
}