use rucc_base::Symbol;
use rucc_diag::Span;
use crate::check::Checker;
use crate::expr::{BitCount, Category, Expr, ExprId, ExprKind};
const FAMILY: &[(&str, BitCount)] = &[
("clz", BitCount::Leading),
("ctz", BitCount::Trailing),
("popcount", BitCount::Ones),
("parity", BitCount::Parity),
("ffs", BitCount::FirstSet),
];
const WIDTHS: &[&str] = &["ll", "l", ""];
fn question(spelled: &str) -> Option<BitCount> {
let stem = spelled.strip_prefix("__builtin_")?;
for &width in WIDTHS {
let Some(head) = stem.strip_suffix(width) else { continue };
if let Some(&(_, count)) = FAMILY.iter().find(|&&(name, _)| name == head) {
return Some(count);
}
}
None
}
impl Checker<'_> {
pub(in crate::check) fn count_builtin_value(
&mut self,
function: Option<Symbol>,
args: &[ExprId],
span: Span,
) -> Option<ExprId> {
let name = function?;
let spelled = self.text(name);
if !spelled.starts_with("__builtin_") {
return None;
}
let count = question(spelled)?;
let &operand = args.first()?;
if self.is_poisoned(operand) {
return Some(self.poison(span));
}
let ty = self.int();
Some(
self.tast
.expr(Expr::new(ExprKind::BitCount { operand, count }, ty, Category::Rvalue), span),
)
}
}
#[cfg(test)]
mod tests {
use rucc_gnu::{Kind, Status};
use super::*;
#[test]
fn all_fifteen_names_are_rows_of_the_table_that_carry_a_signature() {
for &(stem, want) in FAMILY {
for &width in WIDTHS {
let name = format!("__builtin_{stem}{width}");
let Some(feature) = rucc_gnu::lookup(Kind::Builtin, &name) else {
panic!("{name} is answered here and is not in features.toml");
};
assert_eq!(feature.status, Status::Implemented, "{name}");
assert!(!feature.signature.is_empty(), "{name} is checked against its prototype");
assert!(feature.library.is_empty(), "{name} is not a call to anything");
assert_eq!(question(&name), Some(want), "{name}");
}
}
}
#[test]
fn every_signature_answers_in_int_and_asks_about_the_width_its_name_says() {
for &(stem, _) in FAMILY {
let of = if stem == "ffs" { "" } else { "unsigned " };
for (width, spelled) in [("", "int"), ("l", "long"), ("ll", "long long")] {
let name = format!("__builtin_{stem}{width}");
let feature = rucc_gnu::lookup(Kind::Builtin, &name).expect("a row");
assert_eq!(feature.signature, format!("int({of}{spelled})"), "{name}");
}
}
}
#[test]
fn a_name_ending_in_two_ells_is_not_the_one_ending_in_one() {
assert_eq!(question("__builtin_clzll"), Some(BitCount::Leading));
assert_eq!(question("__builtin_clzl"), Some(BitCount::Leading));
assert_eq!(question("__builtin_clz"), Some(BitCount::Leading));
}
#[test]
fn a_name_without_the_prefix_or_outside_the_family_asks_nothing() {
for name in ["ffs", "clz", "popcount", "__builtin_clzlll", "__builtin_cl", "__builtin_"] {
assert_eq!(question(name), None, "{name}");
}
}
}