use llvm_ir::{ConstId, ConstantData, Context, InstrKind, IntPredicate, ValueRef};
pub fn try_fold(ctx: &mut Context, kind: &InstrKind) -> Option<ConstId> {
match kind {
InstrKind::Add { lhs, rhs, .. } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
Some(ctx.const_int(ty, l.wrapping_add(r)))
}
InstrKind::Sub { lhs, rhs, .. } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
Some(ctx.const_int(ty, l.wrapping_sub(r)))
}
InstrKind::Mul { lhs, rhs, .. } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
Some(ctx.const_int(ty, l.wrapping_mul(r)))
}
InstrKind::UDiv { lhs, rhs, .. } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
if r == 0 {
return None;
}
Some(ctx.const_int(ty, l / r))
}
InstrKind::SDiv { lhs, rhs, .. } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
let bits = int_bit_width(ctx, ty)?;
let sl = sign_extend(l, bits);
let sr = sign_extend(r, bits);
if sr == 0 {
return None;
}
let type_min: i64 = if bits >= 64 {
i64::MIN
} else {
-(1i64 << (bits - 1))
};
if sl == type_min && sr == -1 {
return None;
}
Some(ctx.const_int(ty, trunc_bits(sl.wrapping_div(sr) as u64, bits)))
}
InstrKind::URem { lhs, rhs } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
if r == 0 {
return None;
}
Some(ctx.const_int(ty, l % r))
}
InstrKind::SRem { lhs, rhs } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
let bits = int_bit_width(ctx, ty)?;
let sl = sign_extend(l, bits);
let sr = sign_extend(r, bits);
if sr == 0 {
return None;
}
Some(ctx.const_int(ty, trunc_bits(sl.wrapping_rem(sr) as u64, bits)))
}
InstrKind::And { lhs, rhs } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
Some(ctx.const_int(ty, l & r))
}
InstrKind::Or { lhs, rhs } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
Some(ctx.const_int(ty, l | r))
}
InstrKind::Xor { lhs, rhs } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
Some(ctx.const_int(ty, l ^ r))
}
InstrKind::Shl { lhs, rhs, .. } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
let mask = shift_mask(ctx, ty)?;
Some(ctx.const_int(ty, l << (r & mask)))
}
InstrKind::LShr { lhs, rhs, .. } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
let mask = shift_mask(ctx, ty)?;
Some(ctx.const_int(ty, l >> (r & mask)))
}
InstrKind::AShr { lhs, rhs, .. } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
let mask = shift_mask(ctx, ty)?;
let bits = int_bit_width(ctx, ty)?;
let sl = sign_extend(l, bits);
Some(ctx.const_int(ty, trunc_bits((sl >> ((r & mask) as u32)) as u64, bits)))
}
InstrKind::ICmp { pred, lhs, rhs } => {
let (ty, l) = const_int(ctx, *lhs)?;
let (_, r) = const_int(ctx, *rhs)?;
let bits = int_bit_width(ctx, ty)?;
let result = icmp_eval(*pred, l, r, bits) as u64;
Some(ctx.const_int(ctx.i1_ty, result))
}
InstrKind::Select {
cond,
then_val,
else_val,
} => {
let (_, c) = const_int(ctx, *cond)?;
let chosen = if c != 0 { then_val } else { else_val };
match chosen {
ValueRef::Constant(cid) => Some(*cid),
_ => None,
}
}
_ => None,
}
}
pub(crate) fn const_int(ctx: &Context, vref: ValueRef) -> Option<(llvm_ir::TypeId, u64)> {
let cid = match vref {
ValueRef::Constant(id) => id,
_ => return None,
};
match ctx.get_const(cid) {
ConstantData::Int { ty, val } => Some((*ty, *val)),
_ => None,
}
}
fn sign_extend(val: u64, bits: u32) -> i64 {
debug_assert!(bits > 0 && bits <= 64);
let shift = 64u32.saturating_sub(bits);
((val << shift) as i64) >> shift
}
fn trunc_bits(val: u64, bits: u32) -> u64 {
if bits >= 64 {
val
} else {
val & ((1u64 << bits).wrapping_sub(1))
}
}
fn int_bit_width(ctx: &Context, ty: llvm_ir::TypeId) -> Option<u32> {
if let llvm_ir::TypeData::Integer(bits) = ctx.get_type(ty) {
Some(*bits)
} else {
None
}
}
fn shift_mask(ctx: &Context, ty: llvm_ir::TypeId) -> Option<u64> {
if let llvm_ir::TypeData::Integer(bits) = ctx.get_type(ty) {
Some((*bits as u64).saturating_sub(1))
} else {
None
}
}
fn icmp_eval(pred: IntPredicate, l: u64, r: u64, bits: u32) -> bool {
match pred {
IntPredicate::Eq => l == r,
IntPredicate::Ne => l != r,
IntPredicate::Ugt => l > r,
IntPredicate::Uge => l >= r,
IntPredicate::Ult => l < r,
IntPredicate::Ule => l <= r,
IntPredicate::Sgt => sign_extend(l, bits) > sign_extend(r, bits),
IntPredicate::Sge => sign_extend(l, bits) >= sign_extend(r, bits),
IntPredicate::Slt => sign_extend(l, bits) < sign_extend(r, bits),
IntPredicate::Sle => sign_extend(l, bits) <= sign_extend(r, bits),
}
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_ir::{Context, InstrKind, IntArithFlags};
fn c(ctx: &mut Context, v: u64) -> ValueRef {
ValueRef::Constant(ctx.const_int(ctx.i32_ty, v))
}
#[test]
fn fold_add() {
let mut ctx = Context::new();
let kind = InstrKind::Add {
flags: IntArithFlags::default(),
lhs: c(&mut ctx, 3),
rhs: c(&mut ctx, 4),
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i32_ty,
val: 7
}
);
}
#[test]
fn fold_sub_wrapping() {
let mut ctx = Context::new();
let kind = InstrKind::Sub {
flags: IntArithFlags::default(),
lhs: c(&mut ctx, 2),
rhs: c(&mut ctx, 5),
};
let result = try_fold(&mut ctx, &kind).unwrap();
if let ConstantData::Int { val, .. } = ctx.get_const(result) {
assert_eq!(*val, 2u64.wrapping_sub(5));
} else {
panic!("expected Int constant");
}
}
#[test]
fn fold_udiv_by_zero_returns_none() {
let mut ctx = Context::new();
let kind = InstrKind::UDiv {
exact: false,
lhs: c(&mut ctx, 10),
rhs: c(&mut ctx, 0),
};
assert!(try_fold(&mut ctx, &kind).is_none());
}
#[test]
fn fold_icmp_eq() {
let mut ctx = Context::new();
let kind = InstrKind::ICmp {
pred: IntPredicate::Eq,
lhs: c(&mut ctx, 7),
rhs: c(&mut ctx, 7),
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i1_ty,
val: 1
}
);
}
#[test]
fn fold_icmp_slt() {
let mut ctx = Context::new();
let neg1 = c(&mut ctx, u64::MAX);
let zero = c(&mut ctx, 0);
let kind = InstrKind::ICmp {
pred: IntPredicate::Slt,
lhs: neg1,
rhs: zero,
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i1_ty,
val: 1
}
);
}
#[test]
fn fold_select_constant_cond() {
let mut ctx = Context::new();
let cond_true = ValueRef::Constant(ctx.const_int(ctx.i1_ty, 1));
let cond_false = ValueRef::Constant(ctx.const_int(ctx.i1_ty, 0));
let then_c = c(&mut ctx, 42);
let else_c = c(&mut ctx, 99);
let k1 = InstrKind::Select {
cond: cond_true,
then_val: then_c,
else_val: else_c,
};
let k2 = InstrKind::Select {
cond: cond_false,
then_val: then_c,
else_val: else_c,
};
let r1 = try_fold(&mut ctx, &k1).unwrap();
let r2 = try_fold(&mut ctx, &k2).unwrap();
assert_eq!(
ctx.get_const(r1),
&ConstantData::Int {
ty: ctx.i32_ty,
val: 42
}
);
assert_eq!(
ctx.get_const(r2),
&ConstantData::Int {
ty: ctx.i32_ty,
val: 99
}
);
}
#[test]
fn non_constant_operand_returns_none() {
let mut ctx = Context::new();
let arg = ValueRef::Argument(llvm_ir::ArgId(0));
let kind = InstrKind::Add {
flags: IntArithFlags::default(),
lhs: arg,
rhs: c(&mut ctx, 1),
};
assert!(try_fold(&mut ctx, &kind).is_none());
}
fn c8(ctx: &mut Context, v: u64) -> ValueRef {
ValueRef::Constant(ctx.const_int(ctx.i8_ty, v))
}
#[test]
fn shl_i32_mask_is_31() {
let mut ctx = Context::new();
let kind = InstrKind::Shl {
flags: llvm_ir::IntArithFlags::default(),
lhs: c(&mut ctx, 1),
rhs: c(&mut ctx, 32), };
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i32_ty,
val: 1
}
); }
#[test]
fn shl_i32_normal() {
let mut ctx = Context::new();
let kind = InstrKind::Shl {
flags: llvm_ir::IntArithFlags::default(),
lhs: c(&mut ctx, 1),
rhs: c(&mut ctx, 4),
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i32_ty,
val: 16
}
);
}
#[test]
fn shl_i8_mask_is_7() {
let mut ctx = Context::new();
let kind = InstrKind::Shl {
flags: llvm_ir::IntArithFlags::default(),
lhs: c8(&mut ctx, 1),
rhs: c8(&mut ctx, 8), };
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i8_ty,
val: 1
}
); }
#[test]
fn lshr_i32_mask_is_31() {
let mut ctx = Context::new();
let kind = InstrKind::LShr {
exact: false,
lhs: c(&mut ctx, 0x8000_0000),
rhs: c(&mut ctx, 31),
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i32_ty,
val: 1
}
);
}
#[test]
fn ashr_i8_sign_extends() {
let mut ctx = Context::new();
let kind = InstrKind::AShr {
exact: false,
lhs: c8(&mut ctx, 0x80),
rhs: c8(&mut ctx, 7),
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i8_ty,
val: 0xFF
}
); }
#[test]
fn sdiv_i8_signs() {
let mut ctx = Context::new();
let kind = InstrKind::SDiv {
exact: false,
lhs: c8(&mut ctx, 0xFF), rhs: c8(&mut ctx, 2),
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i8_ty,
val: 0
}
); }
#[test]
fn sdiv_i8_negative_by_negative() {
let mut ctx = Context::new();
let kind = InstrKind::SDiv {
exact: false,
lhs: c8(&mut ctx, 0xFC), rhs: c8(&mut ctx, 0xFE), };
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i8_ty,
val: 2
}
);
}
#[test]
fn sdiv_overflow_returns_none() {
let mut ctx = Context::new();
let kind = InstrKind::SDiv {
exact: false,
lhs: c8(&mut ctx, 0x80), rhs: c8(&mut ctx, 0xFF), };
assert!(try_fold(&mut ctx, &kind).is_none());
}
#[test]
fn srem_i8_negative() {
let mut ctx = Context::new();
let kind = InstrKind::SRem {
lhs: c8(&mut ctx, 0xF9), rhs: c8(&mut ctx, 3),
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i8_ty,
val: 0xFF
}
); }
#[test]
fn icmp_slt_i8_negative() {
let mut ctx = Context::new();
let kind = InstrKind::ICmp {
pred: IntPredicate::Slt,
lhs: c8(&mut ctx, 0xFF), rhs: c8(&mut ctx, 0),
};
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i1_ty,
val: 1
}
); }
#[test]
fn icmp_sgt_i32_negative() {
let mut ctx = Context::new();
let kind = InstrKind::ICmp {
pred: IntPredicate::Sgt,
lhs: c(&mut ctx, 0),
rhs: c(&mut ctx, 0xFFFF_FFFF), };
let result = try_fold(&mut ctx, &kind).unwrap();
assert_eq!(
ctx.get_const(result),
&ConstantData::Int {
ty: ctx.i1_ty,
val: 1
}
); }
}