use llvm_native_core::constants::{APFloat, APInt, ConstExpr, Constant, FloatSemantics, GEPOperator};
use llvm_native_core::opcode::Opcode;
use llvm_native_core::types::{Type, TypeId, TypeKind};
use llvm_native_core::value::ValueRef;
use std::collections::HashMap;
use std::fmt;
fn int_ty(bits: u32) -> Type {
Type {
id: TypeId::new(),
kind: TypeKind::Integer { bits },
}
}
fn float_ty() -> Type {
Type {
id: TypeId::new(),
kind: TypeKind::Float,
}
}
fn double_ty() -> Type {
Type {
id: TypeId::new(),
kind: TypeKind::Double,
}
}
fn void_ty() -> Type {
Type {
id: TypeId::new(),
kind: TypeKind::Void,
}
}
fn null_ty() -> Type {
void_ty()
}
#[derive(Debug, Clone)]
pub struct ConstantData {
pub data: Vec<u8>,
pub element_type: TypeKind,
pub num_elements: usize,
}
impl ConstantData {
pub fn new(data: Vec<u8>, element_type: TypeKind, num_elements: usize) -> Self {
ConstantData {
data,
element_type,
num_elements,
}
}
pub fn get_sequential(data: &[u8], element_type: TypeKind, num_elements: usize) -> Self {
ConstantData {
data: data.to_vec(),
element_type,
num_elements,
}
}
pub fn get_string(s: &str, null_terminate: bool) -> Self {
let mut data = s.as_bytes().to_vec();
if null_terminate {
data.push(0);
}
ConstantData {
data,
element_type: TypeKind::Integer { bits: 8 },
num_elements: if null_terminate { s.len() + 1 } else { s.len() },
}
}
pub fn get_element_as_u64(&self, index: usize) -> Option<u64> {
let elem_size = self.get_element_size();
let offset = index * elem_size;
if offset + elem_size > self.data.len() {
return None;
}
match elem_size {
1 => Some(self.data[offset] as u64),
2 => Some(u16::from_le_bytes([self.data[offset], self.data[offset + 1]]) as u64),
4 => Some(u32::from_le_bytes([
self.data[offset],
self.data[offset + 1],
self.data[offset + 2],
self.data[offset + 3],
]) as u64),
8 => Some(u64::from_le_bytes([
self.data[offset],
self.data[offset + 1],
self.data[offset + 2],
self.data[offset + 3],
self.data[offset + 4],
self.data[offset + 5],
self.data[offset + 6],
self.data[offset + 7],
])),
_ => None,
}
}
pub fn get_element_as_f64(&self, index: usize) -> Option<f64> {
self.get_element_as_u64(index).map(f64::from_bits)
}
pub fn get_element_size(&self) -> usize {
match self.element_type {
TypeKind::Integer { bits } => (bits as usize + 7) / 8,
TypeKind::Float => 4,
TypeKind::Double => 8,
TypeKind::Half => 2,
TypeKind::BFloat => 2,
TypeKind::X86FP80 => 10,
TypeKind::FP128 => 16,
TypeKind::PPCFP128 => 16,
_ => 8,
}
}
pub fn len(&self) -> usize {
self.num_elements
}
pub fn is_empty(&self) -> bool {
self.num_elements == 0
}
pub fn is_string(&self) -> bool {
self.element_type == TypeKind::Integer { bits: 8 }
}
pub fn get_as_string(&self) -> Option<String> {
if !self.is_string() {
return None;
}
let end = self
.data
.iter()
.position(|&b| b == 0)
.unwrap_or(self.data.len());
String::from_utf8(self.data[..end].to_vec()).ok()
}
}
#[derive(Debug, Clone)]
pub struct ConstantAggregate {
pub elements: Vec<Constant>,
pub is_packed: bool,
}
impl ConstantAggregate {
pub fn new(elements: Vec<Constant>, is_packed: bool) -> Self {
ConstantAggregate {
elements,
is_packed,
}
}
pub fn get_struct(elements: Vec<Constant>, _packed: bool) -> Constant {
Constant::Struct(elements, void_ty())
}
pub fn get_array(elements: Vec<Constant>) -> Constant {
Constant::Array(elements, void_ty())
}
pub fn get_vector(elements: Vec<Constant>) -> Constant {
Constant::Vector(elements, void_ty())
}
pub fn num_elements(&self) -> usize {
self.elements.len()
}
pub fn is_all_zeros(&self) -> bool {
self.elements.iter().all(|e| e.is_null_value())
}
}
#[derive(Debug, Clone)]
pub struct InlineAsm {
pub asm_string: String,
pub constraints: String,
pub has_side_effects: bool,
pub is_align_stack: bool,
pub dialect: AsmDialect,
pub can_throw: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsmDialect {
ATT,
Intel,
}
impl InlineAsm {
pub fn new(
asm_string: String,
constraints: String,
has_side_effects: bool,
is_align_stack: bool,
dialect: AsmDialect,
can_throw: bool,
) -> Self {
InlineAsm {
asm_string,
constraints,
has_side_effects,
is_align_stack,
dialect,
can_throw,
}
}
pub fn parse(text: &str) -> Option<Self> {
let parts: Vec<&str> = text.split(',').collect();
if parts.len() < 2 {
return None;
}
let asm = parts[0].trim().trim_matches('"');
let constraints = parts[1].trim().trim_matches('"');
let sideeffect = parts.get(2).map_or(false, |s| s.trim() == "sideeffect");
let dialect = if parts.get(4).map_or(false, |s| s.trim() == "inteldialect") {
AsmDialect::Intel
} else {
AsmDialect::ATT
};
Some(InlineAsm {
asm_string: asm.to_string(),
constraints: constraints.to_string(),
has_side_effects: sideeffect,
is_align_stack: false,
dialect,
can_throw: false,
})
}
}
#[derive(Debug, Clone)]
pub struct BlockAddress {
pub function_name: String,
pub block_name: String,
}
#[derive(Debug, Clone)]
pub enum FoldedTerminator {
UnconditionalBranch(String),
Unreachable,
CannotFold,
}
pub struct TerminatorFolder;
impl TerminatorFolder {
pub fn fold_conditional_branch(
condition: &Constant,
true_target: &str,
false_target: &str,
) -> FoldedTerminator {
match condition {
Constant::Int(v, _) => {
if *v == 0 {
FoldedTerminator::UnconditionalBranch(false_target.to_string())
} else {
FoldedTerminator::UnconditionalBranch(true_target.to_string())
}
}
Constant::UInt(v, _) => {
if *v == 0 {
FoldedTerminator::UnconditionalBranch(false_target.to_string())
} else {
FoldedTerminator::UnconditionalBranch(true_target.to_string())
}
}
Constant::Null(_) | Constant::ZeroInitializer(_) | Constant::AggregateZero(_) => {
FoldedTerminator::UnconditionalBranch(false_target.to_string())
}
Constant::Undef(_) | Constant::Poison(_) => {
FoldedTerminator::UnconditionalBranch(true_target.to_string())
}
_ => FoldedTerminator::CannotFold,
}
}
pub fn fold_switch(
value: &Constant,
cases: &[(Constant, String)],
default_target: &str,
) -> FoldedTerminator {
match value {
Constant::Int(v, _) => {
for (case_val, target) in cases {
if let Constant::Int(cv, _) = case_val {
if cv == v {
return FoldedTerminator::UnconditionalBranch(target.clone());
}
}
}
FoldedTerminator::UnconditionalBranch(default_target.to_string())
}
Constant::Undef(_) | Constant::Poison(_) => {
if let Some((_, target)) = cases.first() {
FoldedTerminator::UnconditionalBranch(target.clone())
} else {
FoldedTerminator::UnconditionalBranch(default_target.to_string())
}
}
_ => FoldedTerminator::CannotFold,
}
}
}
pub struct ConstantFoldExt;
impl ConstantFoldExt {
pub fn fold_extract_element(vec: &[Constant], index: &Constant) -> Option<Constant> {
let idx = match index {
Constant::Int(v, _) => *v as usize,
Constant::UInt(v, _) => *v as usize,
_ => return None,
};
vec.get(idx).cloned()
}
pub fn fold_insert_element(
vec: &[Constant],
element: &Constant,
index: &Constant,
) -> Option<Vec<Constant>> {
let idx = match index {
Constant::Int(v, _) => *v as usize,
Constant::UInt(v, _) => *v as usize,
_ => return None,
};
if idx >= vec.len() {
return None;
}
let mut result = vec.to_vec();
result[idx] = element.clone();
Some(result)
}
pub fn fold_shuffle_vector(
v1: &[Constant],
v2: &[Constant],
mask: &[i32],
) -> Option<Vec<Constant>> {
let mut result = Vec::new();
for &idx in mask {
if idx == -1 {
result.push(Constant::Undef(null_ty()));
} else if idx >= 0 && (idx as usize) < v1.len() {
result.push(v1[idx as usize].clone());
} else if (idx as usize) < v1.len() + v2.len() {
result.push(v2[(idx as usize) - v1.len()].clone());
} else {
return None;
}
}
Some(result)
}
fn type_size(ty: &TypeKind) -> usize {
match ty {
TypeKind::Integer { bits } => (*bits as usize + 7) / 8,
TypeKind::Float => 4,
TypeKind::Double => 8,
TypeKind::Half => 2,
TypeKind::Pointer { .. } => 8,
TypeKind::Array { len: _, .. } => 0, TypeKind::FixedVector { len: _, .. } => 0,
TypeKind::ScalableVector { .. } => 0,
_ => 8,
}
}
fn extract_int(c: &Constant) -> Option<(i64, u32)> {
match c {
Constant::Int(v, ty) => {
let bits = match ty.kind {
TypeKind::Integer { bits } => bits,
_ => 64,
};
Some((*v, bits))
}
Constant::UInt(v, ty) => {
let bits = match ty.kind {
TypeKind::Integer { bits } => bits,
_ => 64,
};
Some((*v as i64, bits))
}
_ => None,
}
}
pub fn fold_binop(opcode: Opcode, lhs: &Constant, rhs: &Constant) -> Option<Constant> {
let (l_val, _l_bits) = Self::extract_int(lhs)?;
let (_r_val, _r_bits) = Self::extract_int(rhs)?;
let bits = _l_bits.max(_r_bits);
let l = l_val as u64;
let r = _r_val as u64;
let result: i64 = match opcode {
Opcode::Add => (l.wrapping_add(r)) as i64,
Opcode::Sub => (l.wrapping_sub(r)) as i64,
Opcode::Mul => (l.wrapping_mul(r)) as i64,
Opcode::UDiv => {
if r == 0 {
return None;
}
(l.wrapping_div(r)) as i64
}
Opcode::SDiv => {
if r == 0 {
return None;
}
(l_val.wrapping_div(_r_val))
}
Opcode::URem => {
if r == 0 {
return None;
}
(l.wrapping_rem(r)) as i64
}
Opcode::SRem => {
if r == 0 {
return None;
}
(l_val.wrapping_rem(_r_val))
}
Opcode::And => (l & r) as i64,
Opcode::Or => (l | r) as i64,
Opcode::Xor => (l ^ r) as i64,
Opcode::Shl => {
let shift = r & ((bits as u64) - 1);
(l.wrapping_shl(shift as u32)) as i64
}
Opcode::LShr => {
let shift = r & ((bits as u64) - 1);
(l.wrapping_shr(shift as u32)) as i64
}
Opcode::AShr => {
let shift = r & ((bits as u64) - 1);
(l_val).wrapping_shr(shift as u32)
}
_ => return None,
};
Some(Constant::Int(result, int_ty(bits)))
}
fn extract_f64(c: &Constant) -> Option<f64> {
match c {
Constant::Float(v, _) => Some(*v),
_ => None,
}
}
pub fn fold_fbinop(opcode: Opcode, lhs: &Constant, rhs: &Constant) -> Option<Constant> {
let l_val = Self::extract_f64(lhs)?;
let r_val = Self::extract_f64(rhs)?;
let result = match opcode {
Opcode::FAdd => l_val + r_val,
Opcode::FSub => l_val - r_val,
Opcode::FMul => l_val * r_val,
Opcode::FDiv => {
if r_val == 0.0 {
return None;
}
l_val / r_val
}
Opcode::FRem => l_val % r_val,
_ => return None,
};
Some(Constant::Float(result, double_ty()))
}
pub fn fold_icmp(pred: ICmpPredicate, lhs: &Constant, rhs: &Constant) -> Option<Constant> {
let (l_val, _) = Self::extract_int(lhs)?;
let (r_val, _) = Self::extract_int(rhs)?;
let result = match pred {
ICmpPredicate::EQ => l_val == r_val,
ICmpPredicate::NE => l_val != r_val,
ICmpPredicate::UGT => (l_val as u64) > (r_val as u64),
ICmpPredicate::UGE => (l_val as u64) >= (r_val as u64),
ICmpPredicate::ULT => (l_val as u64) < (r_val as u64),
ICmpPredicate::ULE => (l_val as u64) <= (r_val as u64),
ICmpPredicate::SGT => l_val > r_val,
ICmpPredicate::SGE => l_val >= r_val,
ICmpPredicate::SLT => l_val < r_val,
ICmpPredicate::SLE => l_val <= r_val,
};
Some(if result {
Constant::Int(1, int_ty(1))
} else {
Constant::Int(0, int_ty(1))
})
}
pub fn fold_fcmp(pred: FCmpPredicate, lhs: &Constant, rhs: &Constant) -> Option<Constant> {
let l_val = Self::extract_f64(lhs)?;
let r_val = Self::extract_f64(rhs)?;
let result = match pred {
FCmpPredicate::OEQ => l_val == r_val,
FCmpPredicate::ONE => l_val != r_val,
FCmpPredicate::OGT => l_val > r_val,
FCmpPredicate::OGE => l_val >= r_val,
FCmpPredicate::OLT => l_val < r_val,
FCmpPredicate::OLE => l_val <= r_val,
FCmpPredicate::ORD => !l_val.is_nan() && !r_val.is_nan(),
FCmpPredicate::UNO => l_val.is_nan() || r_val.is_nan(),
FCmpPredicate::UEQ => l_val == r_val || l_val.is_nan() || r_val.is_nan(),
FCmpPredicate::UNE => l_val != r_val || l_val.is_nan() || r_val.is_nan(),
FCmpPredicate::UGT => l_val > r_val || l_val.is_nan() || r_val.is_nan(),
FCmpPredicate::UGE => l_val >= r_val || l_val.is_nan() || r_val.is_nan(),
FCmpPredicate::ULT => l_val < r_val || l_val.is_nan() || r_val.is_nan(),
FCmpPredicate::ULE => l_val <= r_val || l_val.is_nan() || r_val.is_nan(),
};
Some(if result {
Constant::Int(1, int_ty(1))
} else {
Constant::Int(0, int_ty(1))
})
}
pub fn fold_phi(incoming: &[(Constant, String)]) -> Option<Constant> {
if incoming.is_empty() {
return None;
}
let first = &incoming[0].0;
for (val, _) in incoming.iter().skip(1) {
if val != first {
return None;
}
}
Some(first.clone())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ICmpPredicate {
EQ,
NE,
UGT,
UGE,
ULT,
ULE,
SGT,
SGE,
SLT,
SLE,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FCmpPredicate {
OEQ,
ONE,
OGT,
OGE,
OLT,
OLE,
ORD,
UNO,
UEQ,
UNE,
UGT,
UGE,
ULT,
ULE,
}
#[derive(Debug, Clone)]
pub struct ConstantRange {
pub lower: u64,
pub upper: u64,
pub bit_width: u32,
}
impl ConstantRange {
pub fn full(bits: u32) -> Self {
ConstantRange {
lower: 0,
upper: 0,
bit_width: bits,
}
}
pub fn empty(bits: u32) -> Self {
ConstantRange {
lower: 1,
upper: 0,
bit_width: bits,
}
}
pub fn range(lo: u64, hi: u64, bits: u32) -> Self {
let mask = (1u64 << bits.min(64)) - 1;
ConstantRange {
lower: lo,
upper: (hi + 1) & mask,
bit_width: bits,
}
}
pub fn is_empty_set(&self) -> bool {
self.lower == 1 && self.upper == 0
}
pub fn contains(&self, value: u64, _bits: u32) -> bool {
if self.is_empty_set() {
return false;
}
if self.lower <= self.upper {
value >= self.lower && value < self.upper
} else {
value >= self.lower || value < self.upper
}
}
pub fn union_with(&self, other: &ConstantRange) -> ConstantRange {
if self.is_empty_set() {
return other.clone();
}
if other.is_empty_set() {
return self.clone();
}
ConstantRange {
lower: self.lower.min(other.lower),
upper: self.upper.max(other.upper),
bit_width: self.bit_width,
}
}
pub fn intersect_with(&self, other: &ConstantRange) -> ConstantRange {
if self.is_empty_set() || other.is_empty_set() {
return ConstantRange::empty(self.bit_width);
}
ConstantRange {
lower: self.lower.max(other.lower),
upper: self.upper.min(other.upper),
bit_width: self.bit_width,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlobalLinkage {
External,
Internal,
Private,
WeakAny,
WeakODR,
LinkOnceAny,
LinkOnceODR,
AvailableExternally,
Appending,
Common,
ExternalWeak,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlobalVisibility {
Default,
Hidden,
Protected,
}
#[derive(Debug, Clone)]
pub struct GlobalObject {
pub name: String,
pub linkage: GlobalLinkage,
pub visibility: GlobalVisibility,
pub alignment: u32,
}
impl GlobalObject {
pub fn new(name: String) -> Self {
GlobalObject {
name,
linkage: GlobalLinkage::External,
visibility: GlobalVisibility::Default,
alignment: 0,
}
}
pub fn set_linkage(&mut self, linkage: GlobalLinkage) {
self.linkage = linkage;
}
pub fn set_visibility(&mut self, vis: GlobalVisibility) {
self.visibility = vis;
}
pub fn is_declaration(&self) -> bool {
matches!(
self.linkage,
GlobalLinkage::External | GlobalLinkage::ExternalWeak
)
}
pub fn has_local_linkage(&self) -> bool {
matches!(
self.linkage,
GlobalLinkage::Internal | GlobalLinkage::Private
)
}
}
pub struct ReplaceWithConstant;
impl ReplaceWithConstant {
pub fn try_fold(opcode: Opcode, operands: &[Constant]) -> Option<Constant> {
match opcode {
Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::UDiv
| Opcode::SDiv
| Opcode::URem
| Opcode::SRem
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Shl
| Opcode::LShr
| Opcode::AShr => {
if operands.len() >= 2 {
ConstantFoldExt::fold_binop(opcode, &operands[0], &operands[1])
} else {
None
}
}
Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv | Opcode::FRem => {
if operands.len() >= 2 {
ConstantFoldExt::fold_fbinop(opcode, &operands[0], &operands[1])
} else {
None
}
}
Opcode::Select => {
if operands.len() >= 3 {
match &operands[0] {
Constant::Int(v, _) => {
if *v == 0 {
Some(operands[2].clone())
} else {
Some(operands[1].clone())
}
}
Constant::Undef(_) | Constant::Poison(_) => Some(operands[1].clone()),
_ => None,
}
} else {
None
}
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constant_data_string() {
let cd = ConstantData::get_string("hello", true);
assert_eq!(cd.data.len(), 6);
assert_eq!(cd.get_as_string(), Some("hello".to_string()));
}
#[test]
fn test_terminator_fold_br_true() {
let cond = Constant::Int(1, int_ty(1));
let result = TerminatorFolder::fold_conditional_branch(&cond, "then", "else");
match result {
FoldedTerminator::UnconditionalBranch(target) => assert_eq!(target, "then"),
_ => panic!(),
}
}
#[test]
fn test_terminator_fold_br_false() {
let cond = Constant::Int(0, int_ty(1));
let result = TerminatorFolder::fold_conditional_branch(&cond, "then", "else");
match result {
FoldedTerminator::UnconditionalBranch(target) => assert_eq!(target, "else"),
_ => panic!(),
}
}
#[test]
fn test_constant_range() {
let range = ConstantRange::range(10, 20, 32);
assert!(range.contains(15, 32));
assert!(!range.contains(5, 32));
}
#[test]
fn test_fold_binop_add() {
let a = Constant::Int(40, int_ty(32));
let b = Constant::Int(2, int_ty(32));
let result = ConstantFoldExt::fold_binop(Opcode::Add, &a, &b);
match result {
Some(Constant::Int(v, _)) => assert_eq!(v, 42),
_ => panic!(),
}
}
#[test]
fn test_fold_select() {
let cond = Constant::Int(1, int_ty(1));
let true_val = Constant::Int(100, int_ty(32));
let false_val = Constant::Int(200, int_ty(32));
let result = ReplaceWithConstant::try_fold(Opcode::Select, &[cond, true_val, false_val]);
match result {
Some(Constant::Int(v, _)) => assert_eq!(v, 100),
_ => panic!(),
}
}
#[test]
fn test_global_object() {
let mut g = GlobalObject::new("my_global".to_string());
g.set_linkage(GlobalLinkage::Internal);
assert!(g.has_local_linkage());
}
#[test]
fn test_shuffle_vector() {
let v1 = vec![Constant::Int(1, int_ty(32)), Constant::Int(2, int_ty(32))];
let v2 = vec![Constant::Int(3, int_ty(32)), Constant::Int(4, int_ty(32))];
let mask = vec![0, 2, -1];
let result = ConstantFoldExt::fold_shuffle_vector(&v1, &v2, &mask);
assert!(result.is_some());
}
}