use crate::register_intrinsic;
use crate::types::{TypeId, TypeManager};
use crate::value::ValueId;
use crate::value::insn::intrinsic::{as_int_binop, const_u64, mask_for};
use crate::value::insn::{
InstructionId, IntBinop, Intrinsic, IntrinsicApp, IntrinsicId, Mnemonic, RootOp, Simplified,
};
use crate::value::{BodyView, QCodeView};
fn eval_rotate(args: &[(u128, usize)], out_size: usize, left: bool) -> Option<u128> {
let (x, _) = *args.first()?;
let (k, _) = *args.get(1)?;
let bits = (out_size * 8) as u32;
if bits == 0 || bits > 128 {
return None;
}
let mask = mask_for(out_size);
let x = x & mask;
let k = (k % bits as u128) as u32;
let res = if k == 0 {
x
} else if left {
(x << k) | (x >> (bits - k))
} else {
(x >> k) | (x << (bits - k))
};
Some(res & mask)
}
fn eval_rol(args: &[(u128, usize)], out_size: usize) -> Option<u128> {
eval_rotate(args, out_size, true)
}
fn eval_ror(args: &[(u128, usize)], out_size: usize) -> Option<u128> {
eval_rotate(args, out_size, false)
}
fn as_shl_idiom<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
v: ValueId,
) -> Option<(ValueId, u64, Option<ValueId>)> {
if let Some((x, c)) = as_int_binop(view, v, IntBinop::ShiftLeft) {
let cv = const_u64(view, c)?;
return Some((x, cv, Some(c)));
}
if let Some((a, b)) = as_int_binop(view, v, IntBinop::Mul) {
let (x, m) = match (const_u64(view, b), const_u64(view, a)) {
(Some(m), _) => (a, m),
(None, Some(m)) => (b, m),
_ => return None,
};
if m.is_power_of_two() {
return Some((x, m.trailing_zeros() as u64, None));
}
}
None
}
fn recognize_rol(
view: BodyView<'_, '_>,
root: crate::value::InstructionId,
) -> Option<Vec<ValueId>> {
let root_size = view.insn_ref(root).size();
let bits = (root_size * 8) as u64;
if bits == 0 {
return None;
}
let (lhs, rhs) = as_int_binop(view, ValueId::Instruction(root), IntBinop::Or)?;
for (shl_side, shr_side) in [(lhs, rhs), (rhs, lhs)] {
let Some((x1, c1v, c1_amount)) = as_shl_idiom(view, shl_side) else {
continue;
};
let Some((x2, c2)) = as_int_binop(view, shr_side, IntBinop::ShiftRight) else {
continue;
};
if x1 != x2 {
continue;
}
let Some(c2v) = const_u64(view, c2) else {
continue;
};
if c1v == 0 || c2v == 0 || c1v + c2v != bits {
continue;
}
let amount = match c1_amount {
Some(existing) => existing,
None => {
let amt_size = view.shared().types.size_of(view.type_of(c2));
view.shared().get_const(c1v, amt_size)
}
};
return Some(vec![x1, amount]);
}
None
}
fn as_rotate<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
v: ValueId,
) -> Option<(&'static str, ValueId, ValueId)> {
let ValueId::Instruction(id) = v else {
return None;
};
let Mnemonic::Intrinsic(intr) = view.instruction(id).mnemonic() else {
return None;
};
let name = intr.id.name();
if name != "rol" && name != "ror" {
return None;
}
let &[x, k] = intr.args.as_slice() else {
return None;
};
Some((name, x.qualify(id.func), k.qualify(id.func)))
}
fn simplify_rotate(
view: BodyView<'_, '_>,
id: IntrinsicId,
out_size: usize,
args: &[ValueId],
) -> Option<Simplified> {
let &[x, k] = args else {
return None;
};
let bits = (out_size * 8) as u64;
if bits == 0 {
return None;
}
if let Some((inner_name, a, k2)) = as_rotate(view, x) {
let outer_name = id.name();
let is_inverse = (outer_name == "rol" && inner_name == "ror")
|| (outer_name == "ror" && inner_name == "rol");
let same_amount = k == k2
|| match (const_u64(view, k), const_u64(view, k2)) {
(Some(a), Some(b)) => a % bits == b % bits,
_ => false,
};
if is_inverse && same_amount {
return Some(Simplified::Value(a));
}
}
if let Some(c) = const_u64(view, k) {
let r = c % bits;
if r == 0 {
return Some(Simplified::Value(x));
}
if r != c {
let k_size = view.shared().types.size_of(view.type_of(k));
let reduced = view.shared().get_const(r, k_size);
let rotate = IntrinsicApp {
id,
args: vec![x.strip_func(), reduced.strip_func()],
};
return Some(Simplified::Expression(Mnemonic::Intrinsic(rotate)));
}
}
None
}
fn rotate_result_type(types: &TypeManager, args: &[TypeId]) -> TypeId {
types.get_int(types.size_of(args[0]))
}
struct Rol;
impl Intrinsic for Rol {
fn name(&self) -> &'static str {
"rol"
}
fn arity(&self) -> usize {
2
}
fn result_type(&self, types: &TypeManager, args: &[TypeId]) -> TypeId {
rotate_result_type(types, args)
}
fn eval(&self, args: &[(u128, usize)], out_size: usize) -> Option<u128> {
eval_rol(args, out_size)
}
fn root_op(&self) -> Option<RootOp> {
Some(RootOp::IntBinop(IntBinop::Or))
}
fn recognize(&self, view: BodyView<'_, '_>, at: InstructionId) -> Option<Vec<ValueId>> {
recognize_rol(view, at)
}
fn simplify(
&self,
view: BodyView<'_, '_>,
id: IntrinsicId,
out_size: usize,
args: &[ValueId],
) -> Option<Simplified> {
simplify_rotate(view, id, out_size, args)
}
}
struct Ror;
impl Intrinsic for Ror {
fn name(&self) -> &'static str {
"ror"
}
fn arity(&self) -> usize {
2
}
fn result_type(&self, types: &TypeManager, args: &[TypeId]) -> TypeId {
rotate_result_type(types, args)
}
fn eval(&self, args: &[(u128, usize)], out_size: usize) -> Option<u128> {
eval_ror(args, out_size)
}
fn simplify(
&self,
view: BodyView<'_, '_>,
id: IntrinsicId,
out_size: usize,
args: &[ValueId],
) -> Option<Simplified> {
simplify_rotate(view, id, out_size, args)
}
}
register_intrinsic!(Rol);
register_intrinsic!(Ror);
#[cfg(test)]
mod tests {
use crate::value::insn::{IntBinop, IntrinsicId, RootOp, recognizers_for};
#[test]
fn rol_ror_registered_and_resolve() {
let rol = IntrinsicId::from_name("rol").expect("rol registered");
let ror = IntrinsicId::from_name("ror").expect("ror registered");
assert_eq!(rol.name(), "rol");
assert_eq!(ror.name(), "ror");
assert_eq!(rol.desc().arity(), 2);
assert!(IntrinsicId::from_name("nope").is_none());
}
#[test]
fn eval_rol_matches_native() {
let rol = IntrinsicId::from_name("rol").unwrap();
let got = rol.desc().eval(&[(0x1234_5678, 4), (8, 4)], 4).unwrap();
assert_eq!(got as u32, 0x1234_5678u32.rotate_left(8));
}
#[test]
fn eval_ror_matches_native() {
let ror = IntrinsicId::from_name("ror").unwrap();
let got = ror.desc().eval(&[(0x1234_5678, 4), (12, 4)], 4).unwrap();
assert_eq!(got as u32, 0x1234_5678u32.rotate_right(12));
}
#[test]
fn rol_zero_is_identity_eval() {
let rol = IntrinsicId::from_name("rol").unwrap();
let got = rol.desc().eval(&[(0xdead_beef, 4), (0, 4)], 4).unwrap();
assert_eq!(got as u32, 0xdead_beef);
}
#[test]
fn recognizers_indexed_by_root() {
let ids = recognizers_for(RootOp::IntBinop(IntBinop::Or));
assert!(ids.iter().any(|id| id.name() == "rol"));
}
}