use log::trace;
use crate::semantics::default::computation::{Arg, ArgEncoding, AsComputationRef, OutputEncoding};
use crate::semantics::default::ops::Op;
pub mod sexpr;
pub mod smt;
pub trait CodeGenerator {
type T;
fn leaf_const(&mut self, value: i128) -> Self::T;
fn leaf_arg(&mut self, arg_index: usize) -> Term<Self::T>;
fn simplify(&mut self, item: Self::T) -> Self::T {
item
}
fn unknown_op_any(&mut self, name: &str, args: &[Self::T]) -> Self::T;
fn unknown_op1(&mut self, name: &str, item: Self::T) -> Self::T {
self.unknown_op_any(name, &[item])
}
fn unknown_op2(&mut self, name: &str, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op_any(name, &[lhs, rhs])
}
fn unknown_op3(&mut self, name: &str, arg0: Self::T, arg1: Self::T, arg2: Self::T) -> Self::T {
self.unknown_op_any(name, &[arg0, arg1, arg2])
}
fn not(&mut self, item: Self::T) -> Self::T {
self.unknown_op1("not", item)
}
fn crop(&mut self, num_bits: u8, item: Self::T) -> Self::T {
self.select(0, num_bits, item)
}
fn sign_extend(&mut self, num_bits: u8, item: Self::T) -> Self::T {
self.unknown_op1(&format!("sign_extend{num_bits}"), item)
}
fn select(&mut self, num_skip: u8, num_take: u8, item: Self::T) -> Self::T {
self.unknown_op1(&format!("select{num_skip}_{num_take}"), item)
}
fn parity(&mut self, item: Self::T) -> Self::T {
let low8 = self.crop(8, item);
self.popcount(low8)
}
fn is_zero(&mut self, item: Self::T) -> Self::T {
let if_zero = self.leaf_const(1);
let if_nonzero = self.leaf_const(0);
self.if_zero(item, if_zero, if_nonzero)
}
fn byte_mask(&mut self, item: Self::T) -> Self::T {
self.unknown_op1("byte_mask", item)
}
fn bit_mask(&mut self, item: Self::T) -> Self::T {
let all_ones = self.leaf_const(-1);
let negated_bitmask = self.shl(all_ones, item);
self.not(negated_bitmask)
}
fn trailing_zeros(&mut self, item: Self::T) -> Self::T {
self.unknown_op1("trailing_zeros", item)
}
fn leading_zeros(&mut self, item: Self::T) -> Self::T {
self.unknown_op1("leading_zeros", item)
}
fn popcount(&mut self, item: Self::T) -> Self::T {
self.unknown_op1("popcount", item)
}
fn swap_bytes(&mut self, num_bits: u8, item: Self::T) -> Self::T {
self.unknown_op1(&format!("swap_bytes{num_bits}"), item)
}
fn add(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("add", lhs, rhs)
}
fn sub(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("sub", lhs, rhs)
}
fn mul(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("mul", lhs, rhs)
}
fn carryless_mul(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("carryless_mul", lhs, rhs)
}
fn div(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("div", lhs, rhs)
}
fn div_unsigned(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("div_unsigned", lhs, rhs)
}
fn rem(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("rem", lhs, rhs)
}
fn rem_unsigned(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("rem_unsigned", lhs, rhs)
}
fn shl(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("shl", lhs, rhs)
}
fn shr(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("shr", lhs, rhs)
}
fn and(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("and", lhs, rhs)
}
fn or(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("or", lhs, rhs)
}
fn xor(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("xor", lhs, rhs)
}
fn cmp_lt(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("cmp_lt", lhs, rhs)
}
fn rol(&mut self, num_bits: u8, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2(&format!("rol{num_bits}"), lhs, rhs)
}
fn deposit_bits(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("deposit_bits", lhs, rhs)
}
fn extract_bits(&mut self, lhs: Self::T, rhs: Self::T) -> Self::T {
self.unknown_op2("extract_bits", lhs, rhs)
}
fn if_zero(&mut self, condition: Self::T, if_zero: Self::T, if_nonzero: Self::T) -> Self::T {
self.unknown_op3("if_zero", condition, if_zero, if_nonzero)
}
}
#[derive(Copy, Clone, Debug)]
pub enum TermOp {
Crop {
num_bits: u8,
},
SwapBytes {
num_bits: u8,
},
SignExtend {
num_bits: u8,
},
}
impl TermOp {
pub fn apply<C: CodeGenerator>(&self, g: &mut C, item: C::T) -> C::T {
match *self {
TermOp::Crop {
num_bits,
} => g.crop(num_bits, item),
TermOp::SwapBytes {
num_bits,
} => g.swap_bytes(num_bits, item),
TermOp::SignExtend {
num_bits,
} => g.sign_extend(num_bits, item),
}
}
}
#[derive(Clone, Debug)]
pub struct Term<T> {
data: T,
num_bits: usize,
operations: Vec<TermOp>,
}
impl<T> Term<T> {
pub fn simple(data: T) -> Self {
Self {
data,
num_bits: 128,
operations: Vec::new(),
}
}
pub fn new(data: T, num_bits: usize, operations: Vec<TermOp>) -> Self {
Self {
data,
num_bits,
operations,
}
}
fn optimize_operations(mut ops: Vec<TermOp>) -> Vec<TermOp> {
'repeat: loop {
for (index, items) in ops.windows(2).enumerate() {
match [items[0], items[1]] {
[TermOp::SwapBytes {
num_bits: a,
}, TermOp::SwapBytes {
num_bits: b,
}] if a == b => {
ops.remove(index);
ops.remove(index);
continue 'repeat;
},
_ => (),
}
}
break
}
ops
}
pub fn unwrap<C: CodeGenerator<T = T>>(self, g: &mut C) -> T {
let operations = Self::optimize_operations(self.operations);
let mut data = self.data;
for op in operations.iter() {
data = op.apply(g, data)
}
data
}
pub fn unwrap_if_applied(self) -> T {
assert!(self.operations.is_empty());
self.data
}
pub fn map<R>(self, map: impl FnOnce(T) -> R) -> Term<R> {
Term {
data: map(self.data),
num_bits: self.num_bits,
operations: self.operations,
}
}
}
pub fn codegen_computation<C: CodeGenerator>(g: &mut C, computation: &impl AsComputationRef) -> C::T {
let result = codegen_template(
g,
computation.expr().ops(),
computation.arg_interpretation(),
computation.consts(),
);
let num_bits = computation.as_internal().output_type().num_bits();
let cropped = g.crop(num_bits.try_into().unwrap(), result);
match computation.as_internal().output_encoding() {
OutputEncoding::UnsignedLittleEndian => g.swap_bytes(num_bits.try_into().unwrap(), cropped),
OutputEncoding::UnsignedBigEndian => cropped,
}
}
fn apply_fn1<C: CodeGenerator>(g: &mut C, args: &mut Vec<Term<C::T>>, mut f: impl FnMut(&mut C, C::T) -> C::T) -> Term<C::T> {
let lhs = args.remove(0).unwrap(g);
Term::simple(f(g, lhs))
}
fn apply_fn2<C: CodeGenerator>(
g: &mut C, args: &mut Vec<Term<C::T>>, mut f: impl FnMut(&mut C, C::T, C::T) -> C::T,
) -> Term<C::T> {
let lhs = args.remove(0).unwrap(g);
let rhs = args.remove(0).unwrap(g);
Term::simple(f(g, lhs, rhs))
}
pub fn codegen_template<C: CodeGenerator>(g: &mut C, ops: &[Op], arg_interpretation: &[Arg], consts: &[i128]) -> C::T {
let mut stack = Vec::<Term<C::T>>::new();
for &op in ops.iter() {
let mut args = stack.drain(stack.len() - op.num_args()..).collect::<Vec<_>>();
stack.push(match op {
Op::Hole(n) => match arg_interpretation[n as usize] {
Arg::Input {
index,
num_bits,
encoding,
} => {
let mut val = g.leaf_arg(index as usize);
if matches!(encoding, ArgEncoding::SignedLittleEndian | ArgEncoding::UnsignedLittleEndian) {
trace!("Swapping bytes of hole {n} of {num_bits} bits");
val.operations.push(TermOp::SwapBytes {
num_bits,
});
}
if matches!(encoding, ArgEncoding::SignedLittleEndian | ArgEncoding::SignedBigEndian) && num_bits != 128 {
val.operations.push(TermOp::SignExtend {
num_bits,
});
}
val
},
Arg::TinyConst(c) => Term::simple(g.leaf_const(c as i128)),
Arg::Const(index) => Term::simple(g.leaf_const(consts[index as usize])),
},
Op::Const(c) => Term::simple(g.leaf_const(c as i128)),
Op::Add => apply_fn2(g, &mut args, CodeGenerator::add),
Op::Sub => apply_fn2(g, &mut args, CodeGenerator::sub),
Op::Mul => apply_fn2(g, &mut args, CodeGenerator::mul),
Op::CarrylessMul => apply_fn2(g, &mut args, CodeGenerator::carryless_mul),
Op::Div => apply_fn2(g, &mut args, CodeGenerator::div),
Op::UnsignedDiv => apply_fn2(g, &mut args, CodeGenerator::div_unsigned),
Op::Rem => apply_fn2(g, &mut args, CodeGenerator::rem),
Op::UnsignedRem => apply_fn2(g, &mut args, CodeGenerator::rem_unsigned),
Op::Shl => apply_fn2(g, &mut args, CodeGenerator::shl),
Op::Shr => apply_fn2(g, &mut args, CodeGenerator::shr),
Op::And => apply_fn2(g, &mut args, CodeGenerator::and),
Op::Or => apply_fn2(g, &mut args, CodeGenerator::or),
Op::Xor => apply_fn2(g, &mut args, CodeGenerator::xor),
Op::CmpLt => apply_fn2(g, &mut args, CodeGenerator::cmp_lt),
Op::Rol {
num_bits,
} => {
let lhs = args.remove(0).unwrap(g);
let rhs = args.remove(0).unwrap(g);
Term::simple(g.rol(num_bits, lhs, rhs))
},
Op::Not => apply_fn1(g, &mut args, CodeGenerator::not),
Op::Parity => apply_fn1(g, &mut args, CodeGenerator::parity),
Op::IsZero => apply_fn1(g, &mut args, CodeGenerator::is_zero),
Op::Crop {
num_bits,
} => {
let item = args.remove(0).unwrap(g);
Term::simple(g.crop(num_bits, item))
},
Op::SignExtend {
num_bits,
} => {
let item = args.remove(0).unwrap(g);
Term::simple(g.sign_extend(num_bits, item))
},
Op::Select {
num_skip,
num_take,
} => {
let item = args.remove(0).unwrap(g);
Term::simple(g.select(num_skip, num_take, item))
},
Op::ByteMask => apply_fn1(g, &mut args, CodeGenerator::byte_mask),
Op::BitMask => apply_fn1(g, &mut args, CodeGenerator::bit_mask),
Op::TrailingZeros => apply_fn1(g, &mut args, CodeGenerator::trailing_zeros),
Op::LeadingZeros => apply_fn1(g, &mut args, CodeGenerator::leading_zeros),
Op::PopCount => apply_fn1(g, &mut args, CodeGenerator::popcount),
Op::SwapBytes {
num_bits,
} => {
let mut item = args.remove(0);
item.operations.push(TermOp::SwapBytes {
num_bits,
});
item
},
Op::IfZero => {
let condition = args.remove(0).unwrap(g);
let if_zero = args.remove(0).unwrap(g);
let if_nonzero = args.remove(0).unwrap(g);
Term::simple(g.if_zero(condition, if_zero, if_nonzero))
},
Op::DepositBits => apply_fn2(g, &mut args, CodeGenerator::deposit_bits),
Op::ExtractBits => apply_fn2(g, &mut args, CodeGenerator::extract_bits),
});
}
stack.pop().unwrap().unwrap(g)
}
#[cfg(test)]
mod tests {
use super::codegen_template;
use super::sexpr::SExprCodeGen;
use crate::semantics::default::codegen::sexpr::SExpr;
use crate::semantics::default::computation::{Arg, ArgEncoding};
use crate::semantics::default::ops::Op;
#[test]
pub fn double_byteswap_removed() {
let mut g = SExprCodeGen::new();
let generated = codegen_template(
&mut g,
&[
Op::Hole(0),
Op::SwapBytes {
num_bits: 32,
},
],
&[Arg::Input {
index: 0,
num_bits: 32,
encoding: ArgEncoding::UnsignedLittleEndian,
}],
&[],
);
assert_eq!(
generated,
SExpr::Input {
index: 0
}
)
}
}