use rucc_diag::Span;
use rucc_types::{IntKind, IntegerInfo, TypeId, integer_info, pointee};
use crate::check::Checker;
use crate::expr::{Category, Expr, ExprId, ExprKind, OverflowOp};
const FAMILY: &[(&str, OverflowOp)] = &[
("__builtin_add_overflow", OverflowOp::Add),
("__builtin_sub_overflow", OverflowOp::Sub),
("__builtin_mul_overflow", OverflowOp::Mul),
];
const LIMIT: u32 = 128;
pub(in crate::check) fn operation(spelled: &str) -> Option<OverflowOp> {
FAMILY.iter().find(|&&(name, _)| name == spelled).map(|&(_, op)| op)
}
fn common(shapes: [IntegerInfo; 3]) -> IntegerInfo {
let signed = shapes.iter().any(|shape| shape.signed);
let needed = shapes
.iter()
.map(|shape| if signed && !shape.signed { shape.width + 1 } else { shape.width })
.max()
.unwrap_or(0);
let width = if needed <= 32 {
32
} else if needed <= 64 {
64
} else {
LIMIT
};
IntegerInfo::new(signed && needed <= LIMIT, width)
}
impl Checker<'_> {
pub(in crate::check) fn overflow_builtin(
&mut self,
op: OverflowOp,
args: &[ExprId],
span: Span,
) -> ExprId {
let [lhs, rhs, out] = args[..] else { return self.poison(span) };
let written = pointee(&self.types, self.tast[out].ty);
let shapes = [self.tast[lhs].ty, self.tast[rhs].ty, written.unwrap_or(self.tast[out].ty)];
let mut found = [IntegerInfo::new(false, 0); 3];
for (slot, ty) in found.iter_mut().zip(shapes) {
let Some(shape) = integer_info(&self.types, ty, self.cx.target) else {
return self.poison(span);
};
*slot = shape;
}
let at = self.widest(common(found));
let ty = self.types.boolean();
let args = self.tast.add_expr_refs(&[lhs, rhs, out]);
self.tast.expr(Expr::new(ExprKind::Overflow { op, at, args }, ty, Category::Rvalue), span)
}
fn widest(&mut self, shape: IntegerInfo) -> TypeId {
let kind = match (shape.signed, shape.width) {
(true, 32) => IntKind::Int,
(false, 32) => IntKind::UInt,
(true, 64) => IntKind::LongLong,
(false, 64) => IntKind::ULongLong,
(true, _) => IntKind::Int128,
(false, _) => IntKind::UInt128,
};
self.types.int(kind)
}
}
#[cfg(test)]
mod tests {
use rucc_gnu::Kind;
use super::*;
fn shape(signed: bool, width: u32) -> IntegerInfo {
IntegerInfo::new(signed, width)
}
#[test]
fn all_three_names_are_rows_of_the_table_that_carry_no_signature() {
for &(name, _) in FAMILY {
let feature = rucc_gnu::lookup(Kind::Builtin, name).unwrap_or_else(|| {
panic!("{name} is not a row of features.toml");
});
assert!(feature.signature.is_empty(), "{name} has a signature and is type generic");
}
}
#[test]
fn three_unsigned_types_are_done_unsigned_at_the_widest_of_them() {
let all = [shape(false, 32), shape(false, 32), shape(false, 32)];
assert_eq!(common(all), shape(false, 32));
let mixed = [shape(false, 8), shape(false, 64), shape(false, 16)];
assert_eq!(common(mixed), shape(false, 64));
}
#[test]
fn one_signed_type_anywhere_makes_the_arithmetic_signed() {
let operand = [shape(true, 32), shape(false, 32), shape(false, 32)];
assert_eq!(common(operand), shape(true, 64));
let destination = [shape(false, 16), shape(false, 16), shape(true, 16)];
assert_eq!(common(destination), shape(true, 32));
}
#[test]
fn an_unsigned_type_costs_a_bit_once_the_arithmetic_is_signed() {
let just_over = [shape(false, 32), shape(true, 8), shape(true, 8)];
assert_eq!(common(just_over), shape(true, 64));
let still_under = [shape(false, 31), shape(true, 8), shape(true, 8)];
assert_eq!(common(still_under), shape(true, 32));
}
#[test]
fn a_narrow_call_is_still_done_at_thirty_two_bits() {
let narrow = [shape(true, 8), shape(true, 8), shape(true, 8)];
assert_eq!(common(narrow), shape(true, 32));
}
#[test]
fn a_call_needing_one_bit_more_than_the_widest_type_is_done_unsigned() {
let mixed = [shape(false, LIMIT), shape(true, 32), shape(true, LIMIT)];
assert_eq!(common(mixed), shape(false, LIMIT));
let destination = [shape(true, LIMIT), shape(true, LIMIT), shape(false, LIMIT)];
assert_eq!(common(destination), shape(false, LIMIT));
}
#[test]
fn the_same_widths_unsigned_throughout_are_the_ordinary_case() {
let wide = [shape(false, LIMIT), shape(false, LIMIT), shape(false, LIMIT)];
assert_eq!(common(wide), shape(false, LIMIT));
}
#[test]
fn a_wide_operand_is_done_at_its_own_width() {
let wide = [shape(true, 128), shape(true, 32), shape(true, 32)];
assert_eq!(common(wide), shape(true, 128));
let pushed = [shape(false, 64), shape(true, 8), shape(true, 8)];
assert_eq!(common(pushed), shape(true, 128));
}
#[test]
fn a_name_outside_the_family_asks_for_nothing() {
assert_eq!(operation("__builtin_add_overflow"), Some(OverflowOp::Add));
assert_eq!(operation("__builtin_sub_overflow"), Some(OverflowOp::Sub));
assert_eq!(operation("__builtin_mul_overflow"), Some(OverflowOp::Mul));
assert_eq!(operation("__builtin_add_overflow_p"), None);
assert_eq!(operation("add_overflow"), None);
assert_eq!(operation("__builtin_popcount"), None);
}
}