use std::collections::HashMap;
use crate::capture::SourceRange;
use crate::target::TargetModel;
pub const VA_LIST_NAMES: &[&str] = &["__builtin_va_list"];
pub const INT128_TYPEDEF_NAMES: &[(&str, Ty)] =
&[("__int128_t", Ty::Int128), ("__uint128_t", Ty::UInt128)];
pub const UNREACHABLE_BUILTIN: &str = "__builtin_unreachable";
pub const NEVER_RAW: &[&str] = &["self", "Self", "super", "crate", "_"];
pub fn rust_name_of(name: &str) -> String {
if NEVER_RAW.contains(&name) {
return format!("{name}_");
}
name.to_owned()
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PointerId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ArrayId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct FuncTyId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct RecordId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct EnumId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct AtomicId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Ty {
Void,
Bool,
Char,
SChar,
UChar,
Short,
UShort,
Int,
UInt,
Long,
ULong,
LongLong,
ULongLong,
Int128,
UInt128,
Float,
Double,
ComplexFloat,
ComplexDouble,
Pointer(PointerId),
Array(ArrayId),
Func(FuncTyId),
Record(RecordId),
Enum(EnumId),
VaList,
Atomic(AtomicId),
Error,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PointerType {
pub pointee: Ty,
pub konst: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum VmDim {
Fixed(u64),
Len(ObjectId),
Unknown,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ArrayType {
pub elem: Ty,
pub len: u64,
pub elem_const: bool,
pub vla: bool,
pub vla_len: Option<ObjectId>,
pub incomplete: bool,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct FuncType {
pub ret: Ty,
pub params: Vec<Ty>,
pub variadic: bool,
pub prototyped: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum RecordKind {
Struct,
Union,
}
impl RecordKind {
pub fn as_str(self) -> &'static str {
match self {
RecordKind::Struct => "struct",
RecordKind::Union => "union",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Layout {
pub size: u64,
pub align: u64,
}
#[derive(Clone, Debug)]
pub struct BitField {
pub width: u32,
pub bit_offset: u64,
pub signed: bool,
pub storage: String,
pub storage_offset: u64,
pub getter: String,
pub setter: String,
}
impl BitField {
pub fn offset_in_storage(&self) -> u64 {
self.bit_offset - self.storage_offset * 8
}
}
#[derive(Clone, Debug)]
pub struct Field {
pub name: String,
pub anonymous: bool,
pub ty: Ty,
pub is_const: bool,
pub offset: u64,
pub bits: Option<BitField>,
pub flexible: bool,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub enum RustField {
Member(usize),
Bits {
name: String,
offset: u64,
bytes: u64,
},
Pad {
name: String,
bytes: u64,
},
Align {
name: String,
align: u64,
},
}
#[derive(Clone, Debug)]
pub struct RecordDef {
pub kind: RecordKind,
pub tag: Option<String>,
pub rust_name: String,
pub anonymous: bool,
pub fields: Vec<Field>,
pub rust_fields: Vec<RustField>,
pub complete: bool,
pub layout: Option<Layout>,
pub align: Option<u64>,
pub packed: Option<u64>,
pub rust_align: u64,
pub flexible: bool,
pub emit: bool,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub struct Enumerator {
pub name: String,
pub rust_name: String,
pub ty: Ty,
pub value: i128,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub struct EnumDef {
pub unsigned: bool,
pub tag: Option<String>,
pub rust_name: String,
pub anonymous: bool,
pub emit: bool,
pub range: SourceRange,
}
#[derive(Clone, Debug, Default)]
pub struct Types {
pointers: Vec<PointerType>,
arrays: Vec<ArrayType>,
funcs: Vec<FuncType>,
records: Vec<RecordDef>,
enums: Vec<EnumDef>,
atomics: Vec<Ty>,
pointer_index: HashMap<PointerType, PointerId>,
array_index: HashMap<ArrayType, ArrayId>,
func_index: HashMap<FuncType, FuncTyId>,
atomic_index: HashMap<Ty, AtomicId>,
}
impl Types {
pub fn new() -> Self {
Self::default()
}
pub fn pointer(&mut self, pointee: Ty, konst: bool) -> Ty {
let key = PointerType { pointee, konst };
if let Some(id) = self.pointer_index.get(&key) {
return Ty::Pointer(*id);
}
let id = PointerId(self.pointers.len() as u32);
self.pointers.push(key);
self.pointer_index.insert(key, id);
Ty::Pointer(id)
}
pub fn atomic(&mut self, inner: Ty) -> Ty {
if matches!(inner, Ty::Atomic(_)) {
return inner;
}
if let Some(id) = self.atomic_index.get(&inner) {
return Ty::Atomic(*id);
}
let id = AtomicId(self.atomics.len() as u32);
self.atomics.push(inner);
self.atomic_index.insert(inner, id);
Ty::Atomic(id)
}
pub fn atomic_inner(&self, id: AtomicId) -> Ty {
self.atomics[id.0 as usize]
}
pub fn unatomic(&self, ty: Ty) -> Ty {
match ty {
Ty::Atomic(id) => self.atomic_inner(id),
other => other,
}
}
pub fn is_atomic(&self, ty: Ty) -> bool {
matches!(ty, Ty::Atomic(_))
}
pub fn array(&mut self, elem: Ty, len: u64, elem_const: bool) -> Ty {
self.array_type_of(ArrayType {
elem,
len,
elem_const,
vla: false,
vla_len: None,
incomplete: false,
})
}
pub fn vla_array(&mut self, elem: Ty, elem_const: bool, vla_len: Option<ObjectId>) -> Ty {
self.array_type_of(ArrayType {
elem,
len: 0,
elem_const,
vla: true,
vla_len,
incomplete: false,
})
}
pub fn incomplete_array(&mut self, elem: Ty, elem_const: bool) -> Ty {
self.array_type_of(ArrayType {
elem,
len: 0,
elem_const,
vla: false,
vla_len: None,
incomplete: true,
})
}
pub fn const_elements(&mut self, ty: Ty) -> Ty {
let Ty::Array(id) = ty else {
return ty;
};
let array = self.array_type(id);
if array.elem.is_array() {
let elem = self.const_elements(array.elem);
if elem == array.elem {
return ty;
}
return self.array_type_of(ArrayType { elem, ..array });
}
if array.elem_const {
return ty;
}
self.array_type_of(ArrayType {
elem_const: true,
..array
})
}
fn array_type_of(&mut self, key: ArrayType) -> Ty {
if let Some(id) = self.array_index.get(&key) {
return Ty::Array(*id);
}
let id = ArrayId(self.arrays.len() as u32);
self.arrays.push(key);
self.array_index.insert(key, id);
Ty::Array(id)
}
pub fn func(&mut self, ret: Ty, params: Vec<Ty>, variadic: bool) -> Ty {
self.func_type_of(FuncType {
ret,
params,
variadic,
prototyped: true,
})
}
pub fn unprototyped_func(&mut self, ret: Ty) -> Ty {
self.func_type_of(FuncType {
ret,
params: Vec::new(),
variadic: false,
prototyped: false,
})
}
pub fn func_type_of(&mut self, key: FuncType) -> Ty {
if let Some(id) = self.func_index.get(&key) {
return Ty::Func(*id);
}
let id = FuncTyId(self.funcs.len() as u32);
self.funcs.push(key.clone());
self.func_index.insert(key, id);
Ty::Func(id)
}
pub fn add_record(&mut self, def: RecordDef) -> RecordId {
let id = RecordId(self.records.len() as u32);
self.records.push(def);
id
}
pub fn add_enum(&mut self, def: EnumDef) -> EnumId {
let id = EnumId(self.enums.len() as u32);
self.enums.push(def);
id
}
pub fn pointer_type(&self, id: PointerId) -> PointerType {
self.pointers[id.0 as usize]
}
pub fn array_type(&self, id: ArrayId) -> ArrayType {
self.arrays[id.0 as usize]
}
pub fn func_type(&self, id: FuncTyId) -> &FuncType {
&self.funcs[id.0 as usize]
}
pub fn record(&self, id: RecordId) -> &RecordDef {
&self.records[id.0 as usize]
}
pub fn record_mut(&mut self, id: RecordId) -> &mut RecordDef {
&mut self.records[id.0 as usize]
}
pub fn records(&self) -> &[RecordDef] {
&self.records
}
pub fn suppress_records_from(&mut self, mark: usize) {
for def in &mut self.records[mark..] {
def.emit = false;
}
}
pub fn enum_def(&self, id: EnumId) -> &EnumDef {
&self.enums[id.0 as usize]
}
pub fn enum_mut(&mut self, id: EnumId) -> &mut EnumDef {
&mut self.enums[id.0 as usize]
}
pub fn enums(&self) -> &[EnumDef] {
&self.enums
}
pub fn pointee(&self, ty: Ty) -> Option<Ty> {
match ty {
Ty::Pointer(id) => Some(self.pointer_type(id).pointee),
_ => None,
}
}
pub fn points_to_const(&self, ty: Ty) -> bool {
match ty {
Ty::Pointer(id) => self.pointer_type(id).konst,
_ => false,
}
}
pub fn elem(&self, ty: Ty) -> Option<Ty> {
match ty {
Ty::Array(id) => Some(self.array_type(id).elem),
_ => None,
}
}
pub fn is_vla(&self, ty: Ty) -> bool {
matches!(ty, Ty::Array(id) if self.array_type(id).vla)
}
pub fn is_vm(&self, ty: Ty) -> bool {
match ty {
Ty::Array(id) => {
let array = self.array_type(id);
array.vla || self.is_vm(array.elem)
}
_ => false,
}
}
pub fn vm_step_ty(&self, ty: Ty) -> Ty {
match ty {
Ty::Array(id) if self.is_vm(ty) => self.vm_step_ty(self.array_type(id).elem),
other => other,
}
}
pub fn vm_dims(&self, ty: Ty) -> Vec<VmDim> {
let mut out = Vec::new();
let mut ty = ty;
while self.is_vm(ty) {
let Ty::Array(id) = ty else { break };
let array = self.array_type(id);
out.push(match (array.vla, array.vla_len) {
(true, Some(len)) => VmDim::Len(len),
(true, None) => VmDim::Unknown,
(false, _) => VmDim::Fixed(array.len),
});
ty = array.elem;
}
out
}
pub fn is_incomplete_array(&self, ty: Ty) -> bool {
matches!(ty, Ty::Array(id) if self.array_type(id).incomplete)
}
pub fn complete_tentative_array(&mut self, ty: Ty) -> Option<Ty> {
let Ty::Array(id) = ty else { return None };
let array = self.array_type(id);
if !array.incomplete {
return None;
}
Some(self.array(array.elem, 1, array.elem_const))
}
pub fn is_func_pointer(&self, ty: Ty) -> bool {
matches!(self.pointee(ty), Some(Ty::Func(_)))
}
pub fn is_void_pointer(&self, ty: Ty) -> bool {
self.pointee(ty) == Some(Ty::Void)
}
pub fn same_pointee(&self, a: Ty, b: Ty) -> bool {
match (a, b) {
(Ty::Pointer(a), Ty::Pointer(b)) => {
self.pointer_type(a).pointee == self.pointer_type(b).pointee
}
_ => false,
}
}
pub fn decayed(&mut self, ty: Ty, konst: bool) -> Ty {
match ty {
Ty::Array(id) => {
let array = self.array_type(id);
self.pointer(array.elem, array.elem_const || konst)
}
Ty::Func(_) => self.pointer(ty, false),
other => other,
}
}
pub fn is_complete(&self, ty: Ty) -> bool {
match ty {
Ty::Void | Ty::Func(_) | Ty::Error => false,
Ty::Record(id) => self.record(id).complete,
Ty::Array(id) => {
let array = self.array_type(id);
!array.incomplete && self.is_complete(array.elem)
}
_ => true,
}
}
pub fn const_member(&self, ty: Ty) -> Option<&str> {
match ty {
Ty::Record(id) => self.record(id).fields.iter().find_map(|field| {
if field.is_const || self.has_const_elements(field.ty) {
Some(field.name.as_str())
} else {
self.const_member(field.ty)
}
}),
Ty::Array(id) => self.const_member(self.array_type(id).elem),
_ => None,
}
}
fn has_const_elements(&self, ty: Ty) -> bool {
match ty {
Ty::Array(id) => {
let array = self.array_type(id);
array.elem_const || self.has_const_elements(array.elem)
}
_ => false,
}
}
pub fn size_align(&self, ty: Ty, target: &TargetModel) -> Option<Layout> {
Some(match ty {
Ty::Void | Ty::Func(_) | Ty::Error | Ty::VaList => return None,
Ty::Pointer(_) => {
let size = u64::from(target.ptr_bits).div_ceil(8);
Layout { size, align: size }
}
Ty::Array(id) => {
let array = self.array_type(id);
let elem = self.size_align(array.elem, target)?;
if array.vla || array.incomplete {
return None;
}
Layout {
size: elem.size.saturating_mul(array.len),
align: elem.align,
}
}
Ty::Record(id) => self.record(id).layout?,
Ty::Atomic(id) => {
let inner = self.size_align(self.atomic_inner(id), target)?;
Layout {
size: inner.size,
align: inner.size.max(inner.align).max(1),
}
}
Ty::Enum(_) => {
let size = u64::from(target.int_bits).div_ceil(8);
Layout { size, align: size }
}
Ty::ComplexFloat | Ty::ComplexDouble => {
let component = ty.complex_component().size_bytes(target);
Layout {
size: component * 2,
align: component.min(target.max_scalar_align).max(1),
}
}
Ty::Int128 | Ty::UInt128 => Layout {
size: 16,
align: target.int128_align,
},
scalar => {
let size = scalar.size_bytes(target);
Layout {
size,
align: size.min(target.max_scalar_align).max(1),
}
}
})
}
pub fn size_of(&self, ty: Ty, target: &TargetModel) -> Option<u64> {
self.size_align(ty, target).map(|l| l.size)
}
pub fn name(&self, ty: Ty) -> String {
match ty {
Ty::Pointer(id) => {
let p = self.pointer_type(id);
if let Ty::Func(f) = p.pointee {
return self.func_name(f, "(*)");
}
let prefix = if p.konst { "const " } else { "" };
format!("{prefix}{} *", self.name(p.pointee))
}
Ty::Array(id) => {
let a = self.array_type(id);
let prefix = if a.elem_const { "const " } else { "" };
if a.vla {
return format!("{prefix}{}[*]", self.name(a.elem));
}
if a.incomplete {
return format!("{prefix}{}[]", self.name(a.elem));
}
format!("{prefix}{}[{}]", self.name(a.elem), a.len)
}
Ty::Func(id) => self.func_name(id, ""),
Ty::Record(id) => {
let record = self.record(id);
match &record.tag {
Some(tag) => format!("{} {tag}", record.kind.as_str()),
None => format!("{} {}", record.kind.as_str(), record.rust_name),
}
}
Ty::Enum(id) => {
let def = self.enum_def(id);
match &def.tag {
Some(tag) => format!("enum {tag}"),
None => format!("enum {}", def.rust_name),
}
}
Ty::Atomic(id) => format!("_Atomic({})", self.name(self.atomic_inner(id))),
Ty::Error => "<error>".to_owned(),
scalar => scalar.scalar_name().to_owned(),
}
}
fn func_name(&self, id: FuncTyId, middle: &str) -> String {
let f = self.func_type(id);
let mut params: Vec<String> = f.params.iter().map(|p| self.name(*p)).collect();
if f.variadic {
params.push("...".to_owned());
}
if params.is_empty() && f.prototyped {
params.push("void".to_owned());
}
format!("{} {middle}({})", self.name(f.ret), params.join(", "))
}
}
impl Ty {
pub fn scalar_name(self) -> &'static str {
match self {
Ty::Void => "void",
Ty::Bool => "_Bool",
Ty::Char => "char",
Ty::SChar => "signed char",
Ty::UChar => "unsigned char",
Ty::Short => "short",
Ty::UShort => "unsigned short",
Ty::Int => "int",
Ty::UInt => "unsigned int",
Ty::Long => "long",
Ty::ULong => "unsigned long",
Ty::LongLong => "long long",
Ty::ULongLong => "unsigned long long",
Ty::Int128 => "__int128",
Ty::UInt128 => "unsigned __int128",
Ty::Float => "float",
Ty::Double => "double",
Ty::ComplexFloat => "float _Complex",
Ty::ComplexDouble => "double _Complex",
Ty::Pointer(_) => "pointer",
Ty::Array(_) => "array",
Ty::Func(_) => "function",
Ty::Record(_) => "struct",
Ty::Enum(_) => "enum",
Ty::VaList => "va_list",
Ty::Atomic(_) => "_Atomic",
Ty::Error => "<error>",
}
}
pub fn is_void(self) -> bool {
self == Ty::Void
}
pub fn is_bool(self) -> bool {
self == Ty::Bool
}
pub fn is_pointer(self) -> bool {
matches!(self, Ty::Pointer(_))
}
pub fn is_array(self) -> bool {
matches!(self, Ty::Array(_))
}
pub fn is_record(self) -> bool {
matches!(self, Ty::Record(_))
}
pub fn is_func(self) -> bool {
matches!(self, Ty::Func(_))
}
pub fn is_enum(self) -> bool {
matches!(self, Ty::Enum(_))
}
pub fn is_va_list(self) -> bool {
self == Ty::VaList
}
pub fn is_error(self) -> bool {
self == Ty::Error
}
pub fn is_integer(self) -> bool {
matches!(
self,
Ty::Bool
| Ty::Char
| Ty::SChar
| Ty::UChar
| Ty::Short
| Ty::UShort
| Ty::Int
| Ty::UInt
| Ty::Long
| Ty::ULong
| Ty::LongLong
| Ty::ULongLong
| Ty::Int128
| Ty::UInt128
| Ty::Enum(_)
)
}
pub fn is_int128(self) -> bool {
matches!(self, Ty::Int128 | Ty::UInt128)
}
pub fn is_floating(self) -> bool {
matches!(self, Ty::Float | Ty::Double)
}
pub fn is_complex(self) -> bool {
matches!(self, Ty::ComplexFloat | Ty::ComplexDouble)
}
pub fn complex_component(self) -> Ty {
match self {
Ty::ComplexFloat => Ty::Float,
Ty::ComplexDouble => Ty::Double,
other => other,
}
}
pub fn complex_of(self) -> Ty {
match self {
Ty::Float => Ty::ComplexFloat,
Ty::ComplexFloat => Ty::ComplexFloat,
_ => Ty::ComplexDouble,
}
}
pub fn is_arithmetic(self) -> bool {
self.is_integer() || self.is_floating() || self.is_complex()
}
pub fn is_scalar(self) -> bool {
self.is_arithmetic() || self.is_pointer()
}
pub fn is_atomic(self) -> bool {
matches!(self, Ty::Atomic(_))
}
pub fn is_signed(self, target: &TargetModel) -> bool {
match self {
Ty::Char => target.char_signed,
Ty::SChar | Ty::Short | Ty::Int | Ty::Long | Ty::LongLong | Ty::Enum(_) => true,
Ty::Int128 => true,
Ty::Float | Ty::Double | Ty::ComplexFloat | Ty::ComplexDouble => true,
_ => false,
}
}
pub fn bits(self, target: &TargetModel) -> u32 {
match self {
Ty::Void => 0,
Ty::Bool => 1,
Ty::Char | Ty::SChar | Ty::UChar => 8,
Ty::Short | Ty::UShort => target.short_bits,
Ty::Int | Ty::UInt | Ty::Enum(_) => target.int_bits,
Ty::Long | Ty::ULong => target.long_bits,
Ty::LongLong | Ty::ULongLong => target.long_long_bits,
Ty::Int128 | Ty::UInt128 => 128,
Ty::Float => 32,
Ty::Double => 64,
Ty::ComplexFloat => 64,
Ty::ComplexDouble => 128,
Ty::Pointer(_) => target.ptr_bits,
Ty::Array(_) | Ty::Func(_) | Ty::Record(_) | Ty::VaList | Ty::Atomic(_) | Ty::Error => {
0
}
}
}
pub fn size_bytes(self, target: &TargetModel) -> u64 {
match self {
Ty::Void => 1, Ty::Bool => 1,
_ => u64::from(self.bits(target)).div_ceil(8),
}
}
pub fn rank(self) -> u32 {
match self {
Ty::Bool => 1,
Ty::Char | Ty::SChar | Ty::UChar => 2,
Ty::Short | Ty::UShort => 3,
Ty::Int | Ty::UInt | Ty::Enum(_) => 4,
Ty::Long | Ty::ULong => 5,
Ty::LongLong | Ty::ULongLong => 6,
Ty::Int128 | Ty::UInt128 => 7,
Ty::Float => 8,
Ty::Double => 9,
_ => 0,
}
}
pub fn to_unsigned(self) -> Ty {
match self {
Ty::Char | Ty::SChar => Ty::UChar,
Ty::Short => Ty::UShort,
Ty::Int | Ty::Enum(_) => Ty::UInt,
Ty::Long => Ty::ULong,
Ty::LongLong => Ty::ULongLong,
Ty::Int128 => Ty::UInt128,
other => other,
}
}
pub fn min_value(self, target: &TargetModel) -> i128 {
if !self.is_signed(target) {
return 0;
}
let bits = self.bits(target);
if bits >= 128 {
return i128::MIN;
}
-(1i128 << (bits - 1))
}
pub fn max_value(self, target: &TargetModel) -> i128 {
if self == Ty::Bool {
return 1;
}
let bits = self.bits(target);
if bits >= 128 {
return i128::MAX;
}
if self.is_signed(target) {
(1i128 << (bits - 1)) - 1
} else {
(1i128 << bits) - 1
}
}
pub fn can_represent(self, value: i128, target: &TargetModel) -> bool {
value >= self.min_value(target) && value <= self.max_value(target)
}
pub fn wrap(self, value: i128, target: &TargetModel) -> i128 {
if self == Ty::Bool {
return i128::from(value != 0);
}
let bits = self.bits(target);
if bits == 0 || bits >= 128 {
return value;
}
let masked = (value as u128) & (u128::MAX >> (128 - bits));
if self.is_signed(target) && masked >> (bits - 1) != 0 {
(masked | (u128::MAX << bits)) as i128
} else {
masked as i128
}
}
pub fn promote(self, target: &TargetModel) -> Ty {
if self.is_enum() {
return Ty::Int;
}
if !self.is_integer() || self.rank() >= Ty::Int.rank() {
return self;
}
if Ty::Int.can_represent(self.min_value(target), target)
&& Ty::Int.can_represent(self.max_value(target), target)
{
Ty::Int
} else {
Ty::UInt
}
}
pub fn promote_bit_field(self, width: u32, signed: bool, target: &TargetModel) -> Ty {
if !self.is_integer() || width == 0 || width > 127 {
return self.promote(target);
}
let (min, max) = if signed {
(-(1i128 << (width - 1)), (1i128 << (width - 1)) - 1)
} else {
(0, (1i128 << width) - 1)
};
for candidate in [Ty::Int, Ty::UInt] {
if candidate.can_represent(min, target) && candidate.can_represent(max, target) {
return candidate;
}
}
self
}
pub fn promote_argument(self, target: &TargetModel) -> Ty {
if self == Ty::Float {
return Ty::Double;
}
self.promote(target)
}
pub fn usual_arithmetic(lhs: Ty, rhs: Ty, target: &TargetModel) -> Ty {
if lhs.is_complex() || rhs.is_complex() {
let real =
Ty::usual_arithmetic(lhs.complex_component(), rhs.complex_component(), target);
return real.complex_of();
}
if lhs == Ty::Double || rhs == Ty::Double {
return Ty::Double;
}
if lhs == Ty::Float || rhs == Ty::Float {
return Ty::Float;
}
let lhs = lhs.promote(target);
let rhs = rhs.promote(target);
if lhs == rhs {
return lhs;
}
let lhs_signed = lhs.is_signed(target);
if lhs_signed == rhs.is_signed(target) {
return if lhs.rank() >= rhs.rank() { lhs } else { rhs };
}
let (unsigned, signed) = if lhs_signed { (rhs, lhs) } else { (lhs, rhs) };
if unsigned.rank() >= signed.rank() {
unsigned
} else if signed.max_value(target) >= unsigned.max_value(target) {
signed
} else {
signed.to_unsigned()
}
}
pub fn size_ty(target: &TargetModel) -> Ty {
if target.int_bits >= target.ptr_bits {
Ty::UInt
} else if target.long_bits >= target.ptr_bits {
Ty::ULong
} else {
Ty::ULongLong
}
}
pub fn ptrdiff_ty(target: &TargetModel) -> Ty {
if target.int_bits >= target.ptr_bits {
Ty::Int
} else if target.long_bits >= target.ptr_bits {
Ty::Long
} else {
Ty::LongLong
}
}
pub fn wchar_ty(target: &TargetModel) -> Ty {
match (target.wchar_bits, target.wchar_signed) {
(16, true) => Ty::Short,
(16, false) => Ty::UShort,
(_, true) => Ty::Int,
(_, false) => Ty::UInt,
}
}
pub fn char16_ty() -> Ty {
Ty::UShort
}
pub fn char32_ty() -> Ty {
Ty::UInt
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ObjectId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct FuncId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct LoopId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct SwitchId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct LabelId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct StrId(pub u32);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Storage {
Automatic,
Static {
item_name: String,
exported: bool,
},
ThreadLocal {
item_name: String,
exported: bool,
},
Extern {
item_name: String,
},
}
impl Storage {
pub fn item_name(&self) -> Option<&str> {
match self {
Storage::Static { item_name, .. } | Storage::ThreadLocal { item_name, .. } => {
Some(item_name)
}
Storage::Automatic | Storage::Extern { .. } => None,
}
}
pub fn is_thread_local(&self) -> bool {
matches!(self, Storage::ThreadLocal { .. })
}
}
#[derive(Clone, Debug)]
pub struct Object {
pub name: String,
pub ty: Ty,
pub storage: Storage,
pub is_const: bool,
pub is_register: bool,
pub vla_storage: bool,
pub align: Option<u64>,
pub flexible_len: Option<u64>,
pub asm_label: Option<String>,
pub section: Option<String>,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub struct StaticVar {
pub object: ObjectId,
pub init: Expr,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Signature {
pub ret: Ty,
pub params: Vec<Ty>,
pub variadic: bool,
pub prototyped: bool,
}
#[derive(Clone, Debug)]
pub enum Body {
Structured(Vec<Stmt>),
Cfg(crate::cfg::Cfg),
}
#[derive(Clone, Debug)]
pub struct Function {
pub name: String,
pub sig: Signature,
pub params: Vec<ObjectId>,
pub param_names: Vec<Option<String>>,
pub is_static: bool,
pub is_inline: bool,
pub noreturn: bool,
pub inline_hint: Option<InlineHint>,
pub cold: bool,
pub deprecated: Option<Option<String>>,
pub section: Option<String>,
pub asm_label: Option<String>,
pub init_kind: Option<InitKind>,
pub safe: Option<SourceRange>,
pub uses_alloca: bool,
pub locals: Vec<ObjectId>,
pub body: Option<Body>,
pub item_name: Option<String>,
pub env: Vec<EnvParam>,
pub range: SourceRange,
}
#[derive(Clone, Copy, Debug)]
pub struct EnvParam {
pub owner: ObjectId,
pub param: ObjectId,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum InlineHint {
Always,
Never,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum InitKind {
Constructor,
Destructor,
}
impl Function {
pub fn is_extern(&self) -> bool {
self.body.is_none()
}
pub fn item_name(&self) -> &str {
self.item_name.as_deref().unwrap_or(&self.name)
}
pub fn is_nested(&self) -> bool {
self.item_name.is_some()
}
pub fn is_safe(&self) -> bool {
self.safe.is_some()
}
}
#[derive(Clone, Copy, Debug)]
pub struct CallEdge {
pub caller: FuncId,
pub callee: FuncId,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub struct TypedefItem {
pub rust_name: String,
pub ty: Ty,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub struct StrData {
pub values: Vec<u32>,
pub elem: Ty,
}
impl StrData {
pub fn len_with_nul(&self) -> u64 {
self.values.len() as u64 + 1
}
}
#[derive(Clone, Debug, Default)]
pub struct Program {
pub unit_id: u64,
pub types: Types,
pub objects: Vec<Object>,
pub statics: Vec<StaticVar>,
pub externs: Vec<ObjectId>,
pub functions: Vec<Function>,
pub calls: Vec<CallEdge>,
pub typedefs: Vec<TypedefItem>,
pub enum_constants: Vec<Enumerator>,
pub strings: Vec<StrData>,
pub link_libraries: Vec<String>,
pub export: bool,
pub no_std: bool,
pub crate_path: String,
}
pub const DEFAULT_CRATE_PATH: &str = "::cinrs";
impl Program {
pub fn object(&self, id: ObjectId) -> &Object {
&self.objects[id.0 as usize]
}
pub fn function(&self, id: FuncId) -> &Function {
&self.functions[id.0 as usize]
}
pub fn string(&self, id: StrId) -> &StrData {
&self.strings[id.0 as usize]
}
pub fn extern_object_name(&self, symbol: &str) -> String {
format!(
"__cinrs_{:08x}_{}",
self.unit_id as u32,
symbol.replace('$', crate::codegen::DOLLAR)
)
}
pub fn has_externs(&self) -> bool {
!self.externs.is_empty() || self.functions.iter().any(Function::is_extern)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ConstValue {
Int(i128),
Float(f64),
Complex(f64, f64),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(missing_docs)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Rem,
BitAnd,
BitXor,
BitOr,
Shl,
Shr,
}
impl BinOp {
pub fn as_str(self) -> &'static str {
match self {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Rem => "%",
BinOp::BitAnd => "&",
BinOp::BitXor => "^",
BinOp::BitOr => "|",
BinOp::Shl => "<<",
BinOp::Shr => ">>",
}
}
pub fn is_shift(self) -> bool {
matches!(self, BinOp::Shl | BinOp::Shr)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(missing_docs)]
pub enum CmpOp {
Lt,
Gt,
Le,
Ge,
Eq,
Ne,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum LogicalOp {
And,
Or,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BuiltinOp {
Popcount,
Clz,
Ctz,
Ffs,
Parity,
Clrsb,
Bswap,
Overflow(BinOp),
OverflowP(BinOp),
Discard,
Alloca,
Fabs,
Copysign,
FloatOrder(FloatOrder),
FloatClass(FloatClass),
Fpclassify,
ComplexProj,
}
pub fn narrow_nan_bits(bits: u64) -> u32 {
let sign = ((bits >> 63) as u32) << 31;
let payload = ((bits >> 29) & 0x7f_ffff) as u32;
sign | 0x7f80_0000 | payload
}
pub fn widen_nan_bits(bits: u32) -> u64 {
let sign = u64::from(bits >> 31) << 63;
let payload = u64::from(bits & 0x7f_ffff) << 29;
sign | 0x7ff0_0000_0000_0000 | payload
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatOrder {
Greater,
GreaterEqual,
Less,
LessEqual,
LessGreater,
Unordered,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatClass {
IsNan,
IsInf,
IsInfSign,
IsFinite,
IsNormal,
IsSignaling,
SignBit,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum MemOrder {
Relaxed,
Acquire,
Release,
AcqRel,
SeqCst,
}
impl MemOrder {
pub fn rust_name(self) -> &'static str {
match self {
MemOrder::Relaxed => "Relaxed",
MemOrder::Acquire => "Acquire",
MemOrder::Release => "Release",
MemOrder::AcqRel => "AcqRel",
MemOrder::SeqCst => "SeqCst",
}
}
pub fn c_name(self) -> &'static str {
match self {
MemOrder::Relaxed => "memory_order_relaxed",
MemOrder::Acquire => "memory_order_acquire",
MemOrder::Release => "memory_order_release",
MemOrder::AcqRel => "memory_order_acq_rel",
MemOrder::SeqCst => "memory_order_seq_cst",
}
}
pub fn valid_for_load(self) -> bool {
!matches!(self, MemOrder::Release | MemOrder::AcqRel)
}
pub fn valid_for_store(self) -> bool {
!matches!(self, MemOrder::Acquire | MemOrder::AcqRel)
}
pub fn strength(self) -> u8 {
match self {
MemOrder::Relaxed => 0,
MemOrder::Acquire | MemOrder::Release => 1,
MemOrder::AcqRel => 2,
MemOrder::SeqCst => 3,
}
}
pub fn failure_order(self) -> MemOrder {
match self {
MemOrder::Release => MemOrder::Relaxed,
MemOrder::AcqRel => MemOrder::Acquire,
other => other,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AtomicClass {
Bool,
Int {
bytes: u64,
signed: bool,
},
Float {
bytes: u64,
},
Ptr,
}
impl AtomicClass {
pub fn rust_name(self) -> &'static str {
match self {
AtomicClass::Bool => "AtomicBool",
AtomicClass::Ptr => "AtomicPtr",
AtomicClass::Float { bytes } => match bytes {
4 => "AtomicU32",
_ => "AtomicU64",
},
AtomicClass::Int { bytes, signed } => match (bytes, signed) {
(1, true) => "AtomicI8",
(1, false) => "AtomicU8",
(2, true) => "AtomicI16",
(2, false) => "AtomicU16",
(4, true) => "AtomicI32",
(4, false) => "AtomicU32",
(_, true) => "AtomicI64",
(_, false) => "AtomicU64",
},
}
}
pub fn repr_name(self) -> &'static str {
match self {
AtomicClass::Bool => "bool",
AtomicClass::Ptr => "",
AtomicClass::Float { bytes } => match bytes {
4 => "u32",
_ => "u64",
},
AtomicClass::Int { bytes, signed } => match (bytes, signed) {
(1, true) => "i8",
(1, false) => "u8",
(2, true) => "i16",
(2, false) => "u16",
(4, true) => "i32",
(4, false) => "u32",
(_, true) => "i64",
(_, false) => "u64",
},
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AtomicOp {
Load,
Store,
Exchange,
CompareExchange {
weak: bool,
},
SyncCompareSwap {
value_is_old: bool,
},
Rmw {
op: AtomicRmw,
returns_new: bool,
},
TestAndSet,
Clear,
Fence {
signal: bool,
},
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AtomicRmw {
Add,
Sub,
And,
Or,
Xor,
Nand,
}
impl AtomicRmw {
pub fn rust_method(self) -> Option<&'static str> {
Some(match self {
AtomicRmw::Add => "fetch_add",
AtomicRmw::Sub => "fetch_sub",
AtomicRmw::And => "fetch_and",
AtomicRmw::Or => "fetch_or",
AtomicRmw::Xor => "fetch_xor",
AtomicRmw::Nand => return None,
})
}
pub fn c_op(self) -> &'static str {
match self {
AtomicRmw::Add => "+",
AtomicRmw::Sub => "-",
AtomicRmw::And => "&",
AtomicRmw::Or => "|",
AtomicRmw::Xor => "^",
AtomicRmw::Nand => "~&",
}
}
}
#[derive(Clone, Debug)]
pub struct AtomicExpr {
pub op: AtomicOp,
pub class: AtomicClass,
pub value_ty: Ty,
pub ptr: Option<Expr>,
pub value: Option<Expr>,
pub expected: Option<Expr>,
pub success: MemOrder,
pub failure: MemOrder,
}
impl AtomicExpr {
pub fn operands(&self) -> impl Iterator<Item = &Expr> {
self.ptr
.iter()
.chain(self.expected.iter())
.chain(self.value.iter())
}
}
pub fn atomic_class(types: &Types, ty: Ty, target: &TargetModel) -> Option<AtomicClass> {
let ty = types.unatomic(ty);
if ty == Ty::Bool {
return Some(AtomicClass::Bool);
}
if ty.is_integer() {
let bytes = ty.size_bytes(target);
return matches!(bytes, 1 | 2 | 4 | 8).then_some(AtomicClass::Int {
bytes,
signed: ty.is_signed(target),
});
}
if ty.is_floating() {
let bytes = ty.size_bytes(target);
return matches!(bytes, 4 | 8).then_some(AtomicClass::Float { bytes });
}
if ty.is_pointer() && !types.is_func_pointer(ty) {
return Some(AtomicClass::Ptr);
}
None
}
#[derive(Clone, Debug)]
pub struct Place {
pub kind: PlaceKind,
pub ty: Ty,
pub is_const: bool,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub enum PlaceKind {
Object(ObjectId),
Deref(Box<Expr>),
Index {
base: Box<Expr>,
index: Box<Expr>,
},
Field {
base: Box<Place>,
record: RecordId,
index: usize,
},
ComplexPart {
base: Box<Place>,
imag: bool,
},
Str(StrId),
Temporary(Box<Expr>),
CompoundLiteral {
object: ObjectId,
init: Box<Expr>,
},
}
#[derive(Clone, Debug)]
pub struct Expr {
pub kind: ExprKind,
pub ty: Ty,
pub bits: Option<u32>,
pub range: SourceRange,
}
impl Expr {
pub fn new(kind: ExprKind, ty: Ty, range: SourceRange) -> Self {
Self {
kind,
ty,
bits: None,
range,
}
}
pub fn narrowed(mut self, bits: Option<u32>) -> Self {
self.bits = bits;
self
}
pub fn int(value: i128, ty: Ty, range: SourceRange) -> Self {
Self::new(ExprKind::Int(value), ty, range)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Eightbyte {
Int,
Sse,
None,
}
#[derive(Clone, Debug)]
pub enum Callee {
Direct(FuncId),
Indirect(Box<Expr>),
}
#[derive(Clone, Debug)]
pub enum ExprKind {
Int(i128),
Float(f64),
ComplexOf {
re: Box<Expr>,
im: Box<Expr>,
},
Zeroed,
Load(Place),
AddrOf(Place),
FuncAddr(FuncId),
LabelAddr(LabelId),
Assign {
place: Place,
value: Box<Expr>,
},
CompoundAssign {
place: Place,
op: BinOp,
value: Box<Expr>,
compute: Ty,
},
IncDec {
place: Place,
dec: bool,
postfix: bool,
},
Neg(Box<Expr>),
BitNot(Box<Expr>),
Binary {
op: BinOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
PtrOffset {
ptr: Box<Expr>,
index: Box<Expr>,
sub: bool,
},
PtrDiff {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Compare {
op: CmpOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Logical {
op: LogicalOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Cast(Box<Expr>),
Cond {
cond: Box<Expr>,
then_expr: Box<Expr>,
else_expr: Box<Expr>,
},
CondDefault {
value: Box<Expr>,
else_expr: Box<Expr>,
},
StmtExpr {
stmts: Vec<Stmt>,
value: Option<Box<Expr>>,
},
Builtin {
op: BuiltinOp,
args: Vec<Expr>,
},
Atomic(Box<AtomicExpr>),
Comma {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Call {
callee: Callee,
args: Vec<Expr>,
},
RecordLit {
record: RecordId,
fields: Vec<Expr>,
},
UnionLit {
record: RecordId,
index: usize,
value: Box<Expr>,
},
ArrayLit(Vec<Expr>),
ArrayRepeat {
value: Box<Expr>,
len: u64,
},
VaListPristine,
VaArg {
ap: Place,
record: Option<Vec<Eightbyte>>,
},
Unreachable,
VaEnd,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BreakTarget {
Loop(LoopId),
Switch(SwitchId),
}
#[derive(Clone, Debug)]
pub enum Stmt {
Nop,
Expr(Expr),
Let {
object: ObjectId,
init: Expr,
explicit: bool,
},
Vla(Box<VlaDef>),
Cleanup(Box<CleanupDef>),
Block(Vec<Stmt>),
If {
cond: Expr,
then_branch: Box<Stmt>,
else_branch: Option<Box<Stmt>>,
},
While {
id: LoopId,
cond: Expr,
body: Box<Stmt>,
range: SourceRange,
},
DoWhile {
id: LoopId,
body: Box<Stmt>,
cond: Expr,
range: SourceRange,
},
For {
id: LoopId,
init: Vec<Stmt>,
cond: Option<Expr>,
step: Option<Expr>,
body: Box<Stmt>,
range: SourceRange,
},
Switch(Box<Switch>),
SwitchTree(Box<SwitchTree>),
Case {
switch: SwitchId,
value: Option<CaseRange>,
body: Box<Stmt>,
range: SourceRange,
},
Label {
id: LabelId,
body: Box<Stmt>,
range: SourceRange,
},
Region(Box<Region>),
Goto {
id: LabelId,
range: SourceRange,
},
GotoPtr {
target: Expr,
range: SourceRange,
},
Break {
target: BreakTarget,
range: SourceRange,
},
Continue {
id: LoopId,
range: SourceRange,
},
Return {
value: Option<Expr>,
range: SourceRange,
},
}
impl Stmt {
pub fn is_cleanup(&self) -> bool {
matches!(self, Stmt::Cleanup(_))
}
}
#[derive(Clone, Debug)]
pub struct VlaDef {
pub object: ObjectId,
pub storage: ObjectId,
pub count: Expr,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub struct CleanupDef {
pub object: ObjectId,
pub func: FuncId,
pub param: Ty,
pub call: Expr,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub struct Switch {
pub id: SwitchId,
pub scrutinee: Expr,
pub hoisted: Vec<ObjectId>,
pub prelude: Vec<Stmt>,
pub groups: Vec<SwitchGroup>,
pub default_group: Option<usize>,
pub range: SourceRange,
}
#[derive(Clone, Debug)]
pub struct Region {
pub label: LabelId,
pub name: String,
pub kind: RegionKind,
pub body: Vec<Stmt>,
pub falls_out: bool,
pub range: SourceRange,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RegionKind {
Block,
Loop,
}
#[derive(Clone, Debug)]
pub struct SwitchTree {
pub id: SwitchId,
pub scrutinee: Expr,
pub body: Box<Stmt>,
pub range: SourceRange,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct CaseRange {
pub low: i128,
pub high: i128,
}
impl CaseRange {
pub fn single(value: i128) -> Self {
Self {
low: value,
high: value,
}
}
pub fn is_single(self) -> bool {
self.low == self.high
}
pub fn overlaps(self, other: CaseRange) -> bool {
self.low <= other.high && other.low <= self.high
}
}
#[derive(Clone, Debug)]
pub struct SwitchGroup {
pub values: Vec<CaseRange>,
pub body: Vec<Stmt>,
}
pub fn always_terminates(stmts: &[Stmt], functions: &[Function]) -> bool {
stmts
.last()
.is_some_and(|stmt| stmt_always_terminates(stmt, functions))
}
pub fn expr_never_returns(expr: &Expr, functions: &[Function]) -> bool {
match &expr.kind {
ExprKind::Call {
callee: Callee::Direct(id),
..
} => functions
.get(id.0 as usize)
.is_some_and(|func| func.noreturn),
ExprKind::Unreachable => true,
ExprKind::Cast(inner) => expr_never_returns(inner, functions),
ExprKind::Comma { rhs, .. } => expr_never_returns(rhs, functions),
_ => false,
}
}
pub fn calls_a_function(expr: &Expr) -> bool {
let any = |list: &[Expr]| list.iter().any(calls_a_function);
match &expr.kind {
ExprKind::Call { .. } | ExprKind::StmtExpr { .. } => true,
ExprKind::Int(_)
| ExprKind::Float(_)
| ExprKind::Zeroed
| ExprKind::FuncAddr(_)
| ExprKind::LabelAddr(_)
| ExprKind::VaListPristine
| ExprKind::Unreachable
| ExprKind::VaEnd => false,
ExprKind::Load(place) | ExprKind::AddrOf(place) => place_calls_a_function(place),
ExprKind::VaArg { ap, .. } => place_calls_a_function(ap),
ExprKind::Assign { place, value } | ExprKind::CompoundAssign { place, value, .. } => {
place_calls_a_function(place) || calls_a_function(value)
}
ExprKind::IncDec { place, .. } => place_calls_a_function(place),
ExprKind::Neg(inner) | ExprKind::BitNot(inner) | ExprKind::Cast(inner) => {
calls_a_function(inner)
}
ExprKind::Binary { lhs, rhs, .. }
| ExprKind::Compare { lhs, rhs, .. }
| ExprKind::Logical { lhs, rhs, .. }
| ExprKind::PtrDiff { lhs, rhs }
| ExprKind::Comma { lhs, rhs } => calls_a_function(lhs) || calls_a_function(rhs),
ExprKind::ComplexOf { re, im } => calls_a_function(re) || calls_a_function(im),
ExprKind::PtrOffset { ptr, index, .. } => calls_a_function(ptr) || calls_a_function(index),
ExprKind::Cond {
cond,
then_expr,
else_expr,
} => calls_a_function(cond) || calls_a_function(then_expr) || calls_a_function(else_expr),
ExprKind::CondDefault { value, else_expr } => {
calls_a_function(value) || calls_a_function(else_expr)
}
ExprKind::Builtin { args, .. } => any(args),
ExprKind::Atomic(atomic) => atomic.operands().any(calls_a_function),
ExprKind::RecordLit { fields, .. } => any(fields),
ExprKind::UnionLit { value, .. } => calls_a_function(value),
ExprKind::ArrayLit(items) => any(items),
ExprKind::ArrayRepeat { value, .. } => calls_a_function(value),
}
}
fn place_calls_a_function(place: &Place) -> bool {
match &place.kind {
PlaceKind::Object(_) | PlaceKind::Str(_) => false,
PlaceKind::Deref(ptr) => calls_a_function(ptr),
PlaceKind::Index { base, index } => calls_a_function(base) || calls_a_function(index),
PlaceKind::Field { base, .. } | PlaceKind::ComplexPart { base, .. } => {
place_calls_a_function(base)
}
PlaceKind::Temporary(expr) => calls_a_function(expr),
PlaceKind::CompoundLiteral { init, .. } => calls_a_function(init),
}
}
pub fn mentions_object(expr: &Expr, object: ObjectId) -> bool {
let any = |list: &[Expr]| list.iter().any(|e| mentions_object(e, object));
match &expr.kind {
ExprKind::Int(_)
| ExprKind::Float(_)
| ExprKind::Zeroed
| ExprKind::FuncAddr(_)
| ExprKind::LabelAddr(_)
| ExprKind::VaListPristine
| ExprKind::Unreachable
| ExprKind::VaEnd => false,
ExprKind::Load(place) | ExprKind::AddrOf(place) => place_mentions_object(place, object),
ExprKind::VaArg { ap, .. } => place_mentions_object(ap, object),
ExprKind::Assign { place, value } | ExprKind::CompoundAssign { place, value, .. } => {
place_mentions_object(place, object) || mentions_object(value, object)
}
ExprKind::IncDec { place, .. } => place_mentions_object(place, object),
ExprKind::Neg(inner) | ExprKind::BitNot(inner) | ExprKind::Cast(inner) => {
mentions_object(inner, object)
}
ExprKind::Binary { lhs, rhs, .. }
| ExprKind::Compare { lhs, rhs, .. }
| ExprKind::Logical { lhs, rhs, .. }
| ExprKind::PtrDiff { lhs, rhs }
| ExprKind::Comma { lhs, rhs } => {
mentions_object(lhs, object) || mentions_object(rhs, object)
}
ExprKind::ComplexOf { re, im } => {
mentions_object(re, object) || mentions_object(im, object)
}
ExprKind::PtrOffset { ptr, index, .. } => {
mentions_object(ptr, object) || mentions_object(index, object)
}
ExprKind::Cond {
cond,
then_expr,
else_expr,
} => {
mentions_object(cond, object)
|| mentions_object(then_expr, object)
|| mentions_object(else_expr, object)
}
ExprKind::CondDefault { value, else_expr } => {
mentions_object(value, object) || mentions_object(else_expr, object)
}
ExprKind::Call { callee, args } => {
let callee = match callee {
Callee::Direct(_) => false,
Callee::Indirect(target) => mentions_object(target, object),
};
callee || any(args)
}
ExprKind::Builtin { args, .. } => any(args),
ExprKind::Atomic(atomic) => atomic.operands().any(|e| mentions_object(e, object)),
ExprKind::RecordLit { fields, .. } => any(fields),
ExprKind::UnionLit { value, .. } => mentions_object(value, object),
ExprKind::ArrayLit(items) => any(items),
ExprKind::ArrayRepeat { value, .. } => mentions_object(value, object),
ExprKind::StmtExpr { .. } => true,
}
}
fn place_mentions_object(place: &Place, object: ObjectId) -> bool {
match &place.kind {
PlaceKind::Object(id) => *id == object,
PlaceKind::Str(_) => false,
PlaceKind::Deref(ptr) => mentions_object(ptr, object),
PlaceKind::Index { base, index } => {
mentions_object(base, object) || mentions_object(index, object)
}
PlaceKind::Field { base, .. } | PlaceKind::ComplexPart { base, .. } => {
place_mentions_object(base, object)
}
PlaceKind::Temporary(expr) => mentions_object(expr, object),
PlaceKind::CompoundLiteral { init, .. } => mentions_object(init, object),
}
}
fn stmt_always_terminates(stmt: &Stmt, functions: &[Function]) -> bool {
let terminates = |stmt: &Stmt| stmt_always_terminates(stmt, functions);
match stmt {
Stmt::Return { .. } => true,
Stmt::Expr(expr) => expr_never_returns(expr, functions),
Stmt::Block(items) => always_terminates(items, functions),
Stmt::Label { body, .. } => terminates(body),
Stmt::Goto { .. } => true,
Stmt::Region(region) => match region.kind {
RegionKind::Block => {
always_terminates(®ion.body, functions) && !jumps_to(®ion.body, region.label)
}
RegionKind::Loop => !region.falls_out,
},
Stmt::If {
then_branch,
else_branch: Some(else_branch),
..
} => terminates(then_branch) && terminates(else_branch),
Stmt::While { id, cond, body, .. } => {
is_always_true(cond) && !breaks_to(body, BreakTarget::Loop(*id))
}
Stmt::DoWhile { id, body, cond, .. } => {
is_always_true(cond) && !breaks_to(body, BreakTarget::Loop(*id))
}
Stmt::For { id, cond, body, .. } => {
cond.as_ref().is_none_or(is_always_true) && !breaks_to(body, BreakTarget::Loop(*id))
}
Stmt::Switch(switch) => {
switch.default_group.is_some()
&& switch
.groups
.last()
.is_some_and(|group| always_terminates(&group.body, functions))
&& !switch
.groups
.iter()
.flat_map(|group| group.body.iter())
.chain(switch.prelude.iter())
.any(|s| breaks_to(s, BreakTarget::Switch(switch.id)))
}
_ => false,
}
}
pub fn is_always_true(expr: &Expr) -> bool {
match &expr.kind {
ExprKind::Int(v) => *v != 0,
ExprKind::Float(v) => *v != 0.0,
ExprKind::Cast(inner) => is_always_true(inner),
_ => false,
}
}
fn jumps_to(stmts: &[Stmt], label: LabelId) -> bool {
stmts.iter().any(|stmt| stmt_jumps_to(stmt, label))
}
fn stmt_jumps_to(stmt: &Stmt, label: LabelId) -> bool {
let jumps = |stmt: &Stmt| stmt_jumps_to(stmt, label);
match stmt {
Stmt::Goto { id, .. } => *id == label,
Stmt::Block(items) => items.iter().any(jumps),
Stmt::Region(region) => region.body.iter().any(jumps),
Stmt::If {
then_branch,
else_branch,
..
} => jumps(then_branch) || else_branch.as_ref().is_some_and(|s| jumps(s)),
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::For { body, .. }
| Stmt::Label { body, .. }
| Stmt::Case { body, .. } => jumps(body),
Stmt::Switch(switch) => {
switch.prelude.iter().any(jumps)
|| switch
.groups
.iter()
.any(|group| group.body.iter().any(jumps))
}
Stmt::SwitchTree(switch) => jumps(&switch.body),
_ => false,
}
}
fn breaks_to(stmt: &Stmt, target: BreakTarget) -> bool {
let breaks = |stmt: &Stmt| breaks_to(stmt, target);
match stmt {
Stmt::Break { target: found, .. } => *found == target,
Stmt::Block(items) => items.iter().any(breaks),
Stmt::Region(region) => region.body.iter().any(breaks),
Stmt::If {
then_branch,
else_branch,
..
} => breaks(then_branch) || else_branch.as_ref().is_some_and(|s| breaks(s)),
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::For { body, .. }
| Stmt::Label { body, .. }
| Stmt::Case { body, .. } => breaks(body),
Stmt::Switch(switch) => {
switch.prelude.iter().any(breaks)
|| switch
.groups
.iter()
.any(|g| g.body.iter().any(|s| breaks_to(s, target)))
}
Stmt::SwitchTree(switch) => breaks(&switch.body),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
const T: TargetModel = TargetModel::LP64;
#[test]
fn small_types_promote_to_int() {
for ty in [
Ty::Bool,
Ty::Char,
Ty::SChar,
Ty::UChar,
Ty::Short,
Ty::UShort,
] {
assert_eq!(ty.promote(&T), Ty::Int, "{}", ty.scalar_name());
}
assert_eq!(Ty::Int.promote(&T), Ty::Int);
assert_eq!(Ty::UInt.promote(&T), Ty::UInt);
assert_eq!(Ty::Double.promote(&T), Ty::Double);
}
#[test]
fn bit_fields_promote_by_their_width() {
let p = |ty: Ty, width: u32| ty.promote_bit_field(width, ty.is_signed(&T), &T);
assert_eq!(p(Ty::UInt, 31), Ty::Int);
assert_eq!(p(Ty::UInt, 32), Ty::UInt);
assert_eq!(p(Ty::Int, 32), Ty::Int);
assert_eq!(p(Ty::Int, 3), Ty::Int);
assert_eq!(p(Ty::Bool, 1), Ty::Int);
assert_eq!(p(Ty::Char, 8), Ty::Int);
assert_eq!(p(Ty::UChar, 8), Ty::Int);
assert_eq!(p(Ty::UShort, 16), Ty::Int);
assert_eq!(p(Ty::ULong, 31), Ty::Int);
assert_eq!(p(Ty::ULong, 32), Ty::UInt);
assert_eq!(p(Ty::ULong, 33), Ty::ULong);
assert_eq!(p(Ty::Long, 33), Ty::Long);
assert_eq!(p(Ty::LongLong, 32), Ty::Int);
assert_eq!(p(Ty::ULongLong, 40), Ty::ULongLong);
assert_eq!(p(Ty::ULongLong, 64), Ty::ULongLong);
assert_eq!(Ty::Int.promote_bit_field(8, false, &T), Ty::Int);
assert_eq!(Ty::Int.promote_bit_field(32, false, &T), Ty::UInt);
}
#[test]
fn unsigned_short_promotes_to_unsigned_int_on_a_16_bit_target() {
let t = TargetModel {
int_bits: 16,
..TargetModel::ILP32
};
assert_eq!(Ty::UShort.promote(&t), Ty::UInt);
assert_eq!(Ty::Short.promote(&t), Ty::Int);
}
#[test]
fn the_usual_arithmetic_conversions_follow_6_3_1_8() {
let u = |a, b| Ty::usual_arithmetic(a, b, &T);
assert_eq!(u(Ty::Int, Ty::UInt), Ty::UInt);
assert_eq!(u(Ty::Char, Ty::Char), Ty::Int);
assert_eq!(u(Ty::Int, Ty::Long), Ty::Long);
assert_eq!(u(Ty::UInt, Ty::Long), Ty::Long);
assert_eq!(
Ty::usual_arithmetic(Ty::UInt, Ty::Long, &TargetModel::ILP32),
Ty::ULong
);
assert_eq!(u(Ty::ULong, Ty::LongLong), Ty::ULongLong);
assert_eq!(u(Ty::UChar, Ty::Long), Ty::Long);
assert_eq!(u(Ty::Float, Ty::LongLong), Ty::Float);
assert_eq!(u(Ty::Double, Ty::Float), Ty::Double);
}
#[test]
fn int128_outranks_every_standard_integer_type() {
let u = |a, b| Ty::usual_arithmetic(a, b, &T);
assert_eq!(u(Ty::Int128, Ty::LongLong), Ty::Int128);
assert_eq!(u(Ty::Int128, Ty::ULongLong), Ty::Int128);
assert_eq!(u(Ty::UInt128, Ty::LongLong), Ty::UInt128);
assert_eq!(u(Ty::UInt128, Ty::Int128), Ty::UInt128);
assert_eq!(u(Ty::Int128, Ty::Int), Ty::Int128);
assert_eq!(u(Ty::Float, Ty::UInt128), Ty::Float);
assert_eq!(u(Ty::Double, Ty::Int128), Ty::Double);
assert_eq!(Ty::Int128.promote(&T), Ty::Int128);
assert_eq!(Ty::UInt128.promote(&T), Ty::UInt128);
assert_eq!(Ty::Int128.promote_argument(&T), Ty::Int128);
assert_eq!(Ty::Int128.to_unsigned(), Ty::UInt128);
assert!(Ty::Int128.is_signed(&T));
assert!(!Ty::UInt128.is_signed(&T));
assert_eq!(Ty::Int128.size_bytes(&T), 16);
assert_eq!(Ty::UInt128.bits(&T), 128);
}
#[test]
fn int128_constants_are_carried_as_bit_patterns() {
assert_eq!(Ty::UInt128.wrap(-1, &T), -1);
assert_eq!(Ty::Int128.wrap(-1, &T), -1);
assert_eq!(Ty::Int128.wrap(i128::MIN, &T), i128::MIN);
assert_eq!(Ty::Int128.min_value(&T), i128::MIN);
assert_eq!(Ty::Int128.max_value(&T), i128::MAX);
assert_eq!(Ty::UInt128.min_value(&T), 0);
assert_eq!(Ty::UInt128.max_value(&T), i128::MAX);
assert_eq!(Ty::UInt.wrap(-1, &T), 4_294_967_295);
}
#[test]
fn int128_takes_its_alignment_from_the_model() {
let types = Types::new();
let layout = types
.size_align(Ty::UInt128, &T)
.expect("a scalar has a layout");
assert_eq!(layout.size, 16);
assert_eq!(layout.align, 16);
let eight = TargetModel {
int128_align: 8,
..TargetModel::LP64
};
let layout = types
.size_align(Ty::Int128, &eight)
.expect("a scalar has a layout");
assert_eq!(layout.size, 16);
assert_eq!(layout.align, 8);
}
#[test]
fn conversions_wrap() {
assert_eq!(Ty::UChar.wrap(300, &T), 44);
assert_eq!(Ty::SChar.wrap(200, &T), -56);
assert_eq!(Ty::UInt.wrap(-1, &T), 4_294_967_295);
assert_eq!(Ty::Int.wrap(4_294_967_295, &T), -1);
assert_eq!(Ty::Bool.wrap(5, &T), 1);
assert_eq!(Ty::Bool.wrap(0, &T), 0);
assert_eq!(Ty::ULongLong.wrap(-1, &T), u64::MAX as i128);
}
#[test]
fn derived_types_are_interned() {
let mut types = Types::new();
let a = types.pointer(Ty::Int, false);
let b = types.pointer(Ty::Int, false);
let c = types.pointer(Ty::Int, true);
assert_eq!(a, b);
assert_ne!(a, c);
assert!(types.same_pointee(a, c));
assert_eq!(types.pointee(a), Some(Ty::Int));
let arr = types.array(Ty::Char, 4, false);
assert_eq!(arr, types.array(Ty::Char, 4, false));
assert_ne!(arr, types.array(Ty::Char, 5, false));
let f = types.func(Ty::Int, vec![a], false);
assert_eq!(f, types.func(Ty::Int, vec![b], false));
assert_ne!(f, types.func(Ty::Int, vec![a], true));
}
#[test]
fn layouts_follow_natural_alignment() {
let mut types = Types::new();
let arr = types.array(Ty::Char, 5, false);
assert_eq!(
types.size_align(arr, &T),
Some(Layout { size: 5, align: 1 })
);
let ptr = types.pointer(Ty::Void, false);
assert_eq!(
types.size_align(ptr, &T),
Some(Layout { size: 8, align: 8 })
);
let f = types.func(Ty::Void, Vec::new(), false);
assert_eq!(types.size_align(f, &T), None);
}
#[test]
fn names_read_like_c() {
let mut types = Types::new();
let cchar = types.pointer(Ty::Char, true);
assert_eq!(types.name(cchar), "const char *");
let arr = types.array(Ty::Int, 3, false);
assert_eq!(types.name(arr), "int[3]");
let f = types.func(Ty::Int, vec![cchar], true);
let fp = types.pointer(f, false);
assert_eq!(types.name(fp), "int (*)(const char *, ...)");
}
}