use super::{
UnaryOp, BinaryOp, Precision, Width,
Register, REGISTERS, Variable, IntoVariable,
Address, Action, Switch, EBB, Ending,
};
use Precision::*;
use BinaryOp::*;
pub const TEMP: Register = REGISTERS[0];
pub fn build<T>(callback: impl FnOnce(Builder<T>) -> EBB<T>) -> EBB<T> {
callback(Builder::new())
}
pub fn build_block(callback: impl FnOnce(&mut Builder<()>)) -> Box<[Action]> {
let mut b = Builder::new();
callback(&mut b);
b.actions.into()
}
#[derive(Debug)]
struct Guard<T> {
pub actions: Box<[Action]>,
pub condition: Variable,
pub expected: bool,
pub if_fail: EBB<T>,
}
#[derive(Debug)]
pub struct Builder<T> {
pub actions: Vec<Action>,
guards: Vec<Guard<T>>,
}
impl<T> Builder<T> {
pub fn new() -> Self {
Builder {actions: Vec::new(), guards: Vec::new()}
}
pub fn move_(&mut self, dest: impl IntoVariable, src: impl IntoVariable) {
self.actions.push(Action::Move(dest.into(), src.into()));
}
pub fn const_(&mut self, dest: Register, value: i64) {
self.actions.push(Action::Constant(P64, dest.into(), value));
}
pub fn unary64(
&mut self,
op: UnaryOp,
dest: Register,
src: impl IntoVariable,
) {
self.actions.push(Action::Unary(op, P64, dest.into(), src.into()));
}
pub fn unary32(
&mut self,
op: UnaryOp,
dest: Register,
src: impl IntoVariable,
) {
self.actions.push(Action::Unary(op, P32, dest.into(), src.into()));
}
pub fn binary64(
&mut self,
op: BinaryOp,
dest: Register,
src1: impl IntoVariable,
src2: impl IntoVariable,
) {
self.actions.push(Action::Binary(op, P64, dest.into(), src1.into(), src2.into()));
}
pub fn binary32(
&mut self,
op: BinaryOp,
dest: Register,
src1: impl IntoVariable,
src2: impl IntoVariable,
) {
self.actions.push(Action::Binary(op, P32, dest.into(), src1.into(), src2.into()));
}
pub fn const_binary64(
&mut self,
op: BinaryOp,
dest: Register,
src: impl IntoVariable,
value: i64,
) {
let src = src.into();
assert_ne!(src, TEMP.into(), "src cannot be TEMP");
self.const_(TEMP, value);
self.binary64(op, dest, src, TEMP);
}
pub fn const_binary32(
&mut self,
op: BinaryOp,
dest: Register,
src: impl IntoVariable,
value: i32,
) {
let src = src.into();
assert_ne!(src, TEMP.into(), "src cannot be TEMP");
self.const_(TEMP, value as i64);
self.binary32(op, dest, src, TEMP);
}
pub fn send(
&mut self,
dest: impl IntoVariable,
src: impl IntoVariable,
) {
self.actions.push(Action::Send(TEMP, dest.into(), src.into()));
self.move_(dest, TEMP);
}
pub fn load(
&mut self,
dest: Register,
addr: (impl IntoVariable, i32, Width),
) {
let (base, offset, width) = addr;
let base = base.into();
self.actions.push(Action::Load(dest.into(), Address {base, offset, width}));
}
pub fn store(
&mut self,
src: impl IntoVariable,
addr: (impl IntoVariable, i32, Width),
) {
let (base, offset, width) = addr;
let base = base.into();
self.actions.push(Action::Store(TEMP, src.into(), Address {base, offset, width}));
self.move_(base, TEMP);
}
pub fn array_load(
&mut self,
dest: Register,
addr: (impl IntoVariable, impl IntoVariable),
width: Width,
) {
self.const_binary64(Lsl, TEMP, addr.1, width as i64);
self.binary64(Add, TEMP, addr.0, TEMP);
self.actions.push(Action::Load(dest.into(), Address {base: TEMP.into(), offset: 0, width}));
self.send(addr.0, TEMP);
}
pub fn array_store(
&mut self,
src: impl IntoVariable,
addr: (impl IntoVariable, impl IntoVariable),
width: Width,
) {
self.const_binary64(Lsl, TEMP, addr.1, width as i64);
self.binary64(Add, TEMP, addr.0, TEMP);
self.actions.push(Action::Store(TEMP, src.into(), Address {base: TEMP.into(), offset: 0, width}));
self.send(addr.0, TEMP);
}
pub fn debug(&mut self, src: impl IntoVariable) {
self.actions.push(Action::Debug(src.into()));
}
pub fn guard(&mut self, condition: impl IntoVariable, expected: bool, if_fail: EBB<T>) {
let mut actions = Vec::new();
std::mem::swap(&mut actions, &mut self.actions);
self.guards.push(Guard {
actions: actions.into(),
condition: condition.into(),
expected,
if_fail,
});
}
pub fn ending(mut self, ending: Ending<T>) -> EBB<T> {
let mut ret = EBB {actions: self.actions.into(), ending};
while let Some(Guard {actions, condition, expected, if_fail}) = self.guards.pop() {
let switch = if expected {
Switch::if_(ret, if_fail)
} else {
Switch::if_(if_fail, ret)
};
ret = EBB {actions, ending: Ending::Switch(condition, switch)};
}
ret
}
pub fn jump(self, target: T) -> EBB<T> {
self.ending(Ending::Leaf(target))
}
pub fn switch(self, discriminant: impl IntoVariable, switch: Switch<EBB<T>>) -> EBB<T> {
self.ending(Ending::Switch(discriminant.into(), switch))
}
pub fn index(
self,
discriminant: impl IntoVariable,
cases: Box<[EBB<T>]>,
default_: EBB<T>,
) -> EBB<T> {
self.switch(discriminant, Switch::new(cases, default_))
}
pub fn if_(
self,
condition: impl IntoVariable,
if_true: EBB<T>,
if_false: EBB<T>,
) -> EBB<T> {
self.switch(condition, Switch::if_(if_true, if_false))
}
}
impl<T> Default for Builder<T> {
fn default() -> Self { Self::new() }
}