use rucc_ast as ast;
use rucc_base::Symbol;
use rucc_base::float::Float;
use rucc_diag::Span;
use rucc_lex::Encoding;
use rucc_types::{FloatKind, float_format};
use crate::check::Checker;
use crate::expr::ExprId;
use crate::tast::Const;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Answer {
Infinity(FloatKind),
Nan(FloatKind, bool),
Length,
Compare,
}
impl Answer {
const fn arity(self) -> usize {
match self {
Answer::Infinity(_) => 0,
Answer::Nan(..) => 1,
Answer::Length => 1,
Answer::Compare => 2,
}
}
}
const TABLE: &[(&str, Answer)] = &[
("__builtin_inf", Answer::Infinity(FloatKind::Double)),
("__builtin_inff", Answer::Infinity(FloatKind::Float)),
("__builtin_infl", Answer::Infinity(FloatKind::LongDouble)),
("__builtin_huge_val", Answer::Infinity(FloatKind::Double)),
("__builtin_huge_valf", Answer::Infinity(FloatKind::Float)),
("__builtin_huge_vall", Answer::Infinity(FloatKind::LongDouble)),
("__builtin_nan", Answer::Nan(FloatKind::Double, true)),
("__builtin_nanf", Answer::Nan(FloatKind::Float, true)),
("__builtin_nanl", Answer::Nan(FloatKind::LongDouble, true)),
("__builtin_nans", Answer::Nan(FloatKind::Double, false)),
("__builtin_nansf", Answer::Nan(FloatKind::Float, false)),
("__builtin_nansl", Answer::Nan(FloatKind::LongDouble, false)),
("__builtin_strlen", Answer::Length),
("__builtin_strcmp", Answer::Compare),
];
impl Checker<'_> {
pub(super) fn constant_builtin_call(
&mut self,
name: Symbol,
args: ast::ExprList,
span: Span,
) -> Option<ExprId> {
let spelled = self.text(name);
let &(_, answer) = TABLE.iter().find(|(row, _)| *row == spelled)?;
let written: Vec<ast::ExprId> = self.ast[args].to_vec();
if written.len() != answer.arity() {
return None;
}
match answer {
Answer::Infinity(kind) => {
let format = float_format(kind, self.cx.target);
Some(self.folded_float(kind, Float::infinity(format, false), span))
}
Answer::Nan(kind, quiet) => {
let payload = payload(&self.literal(written[0])?)?;
let format = float_format(kind, self.cx.target);
Some(self.folded_float(kind, Float::nan_with(format, false, quiet, payload), span))
}
Answer::Length => {
let bytes = self.literal(written[0])?;
let length = length(&bytes);
let ty = self.size_type();
Some(self.constant(Const::Int(i128::from(length)), ty, span))
}
Answer::Compare => {
let left = self.literal(written[0])?;
let right = self.literal(written[1])?;
let order = compare(&left, &right);
let ty = self.int();
Some(self.constant(Const::Int(i128::from(order)), ty, span))
}
}
}
fn folded_float(&mut self, kind: FloatKind, value: Float, span: Span) -> ExprId {
let ty = self.types.float(kind);
self.constant(Const::Float(value), ty, span)
}
fn literal(&self, arg: ast::ExprId) -> Option<Vec<u8>> {
let ast::Expr::Str(id) = self.ast[arg] else { return None };
let literal = &self.ast[id];
matches!(literal.encoding, Encoding::Plain | Encoding::Utf8)
.then(|| literal.bytes(self.cx.target))
}
}
fn length(bytes: &[u8]) -> u64 {
bytes.iter().position(|byte| *byte == 0).unwrap_or(bytes.len()) as u64
}
fn compare(left: &[u8], right: &[u8]) -> i32 {
for (left, right) in left.iter().zip(right) {
if left != right {
return if left < right { -1 } else { 1 };
}
if *left == 0 {
return 0;
}
}
0
}
fn payload(bytes: &[u8]) -> Option<u128> {
let text = std::str::from_utf8(&bytes[..length(bytes) as usize]).ok()?;
let text = text.trim_start_matches([' ', '\t', '\n', '\r', '\x0b', '\x0c']);
let text = text.strip_prefix(['+', '-']).unwrap_or(text);
if text.is_empty() {
return Some(0);
}
let (radix, digits) = match text.as_bytes() {
[b'0', b'x' | b'X', rest @ ..] => (16, rest),
[b'0', rest @ ..] => (8, rest),
rest => (10, rest),
};
if digits.is_empty() {
return Some(0);
}
let mut value: u128 = 0;
for &byte in digits {
let digit = char::from(byte).to_digit(radix)?;
value = value.wrapping_mul(u128::from(radix)).wrapping_add(u128::from(digit));
}
Some(value)
}
#[cfg(test)]
mod tests {
use rucc_gnu::{Kind, Status};
use super::*;
#[test]
fn every_name_answered_here_is_a_row_of_the_table_and_says_it_is_done() {
for (name, _) in TABLE {
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 called when it does not fold");
}
}
#[test]
fn no_name_is_in_the_table_twice() {
let mut names: Vec<&str> = TABLE.iter().map(|(name, _)| *name).collect();
names.sort_unstable();
let all = names.len();
names.dedup();
assert_eq!(names.len(), all, "a name is in the table twice");
}
fn text(spelled: &str) -> Vec<u8> {
let mut bytes = spelled.as_bytes().to_vec();
bytes.push(0);
bytes
}
#[test]
fn a_payload_is_read_the_way_strtol_reads_a_number() {
for (spelled, value) in [
("", 0),
("0", 0),
("1", 1),
("0x1", 1),
("0X1", 1),
("010", 8),
("0xff", 255),
("8", 8),
("-1", 1),
("+2", 2),
(" 1", 1),
("\t 0x10", 16),
("0x", 0),
] {
assert_eq!(payload(&text(spelled)), Some(value), "for {spelled:?}");
}
for spelled in ["abc", "1x", "1 ", "08", "0x1g", "0x1p3", "--1"] {
assert_eq!(payload(&text(spelled)), None, "for {spelled:?}");
}
assert_eq!(payload(b"1\x002\0"), Some(1));
}
#[test]
fn a_length_stops_at_the_first_zero_and_an_order_is_over_unsigned_bytes() {
assert_eq!(length(&text("")), 0);
assert_eq!(length(&text("abc")), 3);
assert_eq!(length(b"a\0bc\0"), 1);
assert_eq!(compare(&text("X"), &text("X")), 0);
assert_eq!(compare(&text("a"), &text("b")), -1);
assert_eq!(compare(&text("b"), &text("a")), 1);
assert_eq!(compare(&text("a"), &text("ab")), -1);
assert_eq!(compare(&text("X"), b"X\xfe\0"), -1);
assert_eq!(compare(b"a\0x\0", b"a\0y\0"), 0);
}
}