use rucc_base::Symbol;
use rucc_diag::Span;
use rucc_gnu::Kind;
use rucc_types::{FloatKind, FunctionType, IntKind, Qualifiers, TypeId, int_width};
use crate::check::Checker;
use crate::decl::{Decl, DeclId, DeclKind, DeclList, Definition, Linkage, StorageDuration};
use crate::scope::Binding;
mod generic;
impl Checker<'_> {
pub(in crate::check) fn declare_builtin(&mut self, name: Symbol, span: Span) -> Option<DeclId> {
let spelled = self.text(name);
if !spelled.starts_with("__") {
return None;
}
let feature = rucc_gnu::lookup(Kind::Builtin, spelled)?;
if feature.signature.is_empty() {
return None;
}
let ty = self.signature_type(feature.signature)?;
let decl = self.tast.decl(
Decl {
name: Some(name),
ty,
kind: DeclKind::Function,
linkage: Linkage::External,
duration: StorageDuration::Static,
state: Definition::Declared,
alignment: None,
init: None,
params: DeclList::EMPTY,
body: None,
},
span,
);
self.scopes.declare_at_file_scope(name, Binding::Decl(decl));
Some(decl)
}
fn signature_type(&mut self, signature: &str) -> Option<TypeId> {
let (result, rest) = signature.split_once('(')?;
let params = rest.strip_suffix(')')?;
let ret = self.written_type(result)?;
let mut types = Vec::new();
let mut variadic = false;
for param in params.split(',').map(str::trim).filter(|param| !param.is_empty()) {
if param == "..." {
variadic = true;
continue;
}
let ty = self.written_type(param)?;
if rucc_types::is_void(&self.types, ty) {
continue;
}
types.push(ty);
}
Some(self.types.function(FunctionType { ret, params: types, variadic, prototyped: true }))
}
fn written_type(&mut self, text: &str) -> Option<TypeId> {
let stars = text.bytes().filter(|byte| *byte == b'*').count();
let words = text.trim_end_matches(['*', ' ']).split_whitespace();
let mut quals = Qualifiers::NONE;
let mut base = Vec::new();
for word in words {
match word {
"const" => quals = quals.with(Qualifiers::CONST),
"volatile" => quals = quals.with(Qualifiers::VOLATILE),
other => base.push(other),
}
}
let mut ty = self.base_type(&base.join(" "))?;
ty = self.types.qualified(ty, quals);
for _ in 0..stars {
ty = self.types.pointer(ty);
}
Some(ty)
}
fn base_type(&mut self, words: &str) -> Option<TypeId> {
let kind = match words {
"void" => return Some(self.types.void()),
"_Bool" => return Some(self.types.boolean()),
"float" => return Some(self.types.float(FloatKind::Float)),
"double" => return Some(self.types.float(FloatKind::Double)),
"long double" => return Some(self.types.float(FloatKind::LongDouble)),
"size_t" => return Some(self.size_type()),
"uint16_t" => return Some(self.exact_unsigned(16)),
"uint32_t" => return Some(self.exact_unsigned(32)),
"uint64_t" => return Some(self.exact_unsigned(64)),
"char" => IntKind::Char,
"signed char" => IntKind::SChar,
"unsigned char" => IntKind::UChar,
"short" | "signed short" | "short int" => IntKind::Short,
"unsigned short" => IntKind::UShort,
"int" | "signed" | "signed int" => IntKind::Int,
"unsigned" | "unsigned int" => IntKind::UInt,
"long" | "signed long" | "long int" => IntKind::Long,
"unsigned long" => IntKind::ULong,
"long long" | "signed long long" => IntKind::LongLong,
"unsigned long long" => IntKind::ULongLong,
_ => return None,
};
Some(self.types.int(kind))
}
fn exact_unsigned(&self, bits: u32) -> TypeId {
let kinds = [IntKind::UChar, IntKind::UShort, IntKind::UInt, IntKind::ULong];
let kind = kinds.into_iter().find(|&kind| int_width(kind, self.cx.target) == bits);
self.types.int(kind.unwrap_or(IntKind::ULongLong))
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_gnu::Kind;
use rucc_session::Std;
use rucc_target::{TargetInfo, Triple};
use rucc_types::TypeKind;
use super::*;
use crate::check::Context;
struct Fixture {
ast: rucc_ast::Ast,
names: Interner,
target: TargetInfo,
}
impl Fixture {
fn new(triple: &str) -> Fixture {
let target = TargetInfo::new(triple.parse::<Triple>().expect("a triple"));
Fixture { ast: rucc_ast::Ast::new(), names: Interner::new(), target }
}
fn checker(&self) -> Checker<'_> {
Checker::new(&self.ast, Context::new(&self.names, &self.target, Std::C23))
}
}
fn spelled(checker: &Checker<'_>, ty: TypeId) -> String {
rucc_types::spell(&checker.types, checker.cx.names, ty)
}
#[test]
fn a_signature_becomes_the_type_it_is_written_as() {
let fixture = Fixture::new("x86_64-unknown-linux-gnu");
let mut c = fixture.checker();
let cases = [
("int(unsigned int)", "int (unsigned int)"),
("long(long, long)", "long (long, long)"),
("void(void)", "void (void)"),
("size_t(const char *)", "unsigned long (const char *)"),
("void *(void *, const void *, size_t)", "void *(void *, const void *, unsigned long)"),
("void(const void *, ...)", "void (const void *, ...)"),
("double(double)", "double (double)"),
("long double(long double)", "long double (long double)"),
];
for (signature, written) in cases {
let ty = c.signature_type(signature).expect("a type");
assert_eq!(spelled(&c, ty), written, "for {signature}");
}
}
#[test]
fn a_void_parameter_list_is_an_empty_one() {
let fixture = Fixture::new("x86_64-unknown-linux-gnu");
let mut c = fixture.checker();
let ty = c.signature_type("void(void)").expect("a type");
let TypeKind::Function(id) = c.types.kind(ty) else { panic!("a function") };
let signature = c.types.signature(id);
assert!(signature.params.is_empty());
assert!(signature.prototyped, "or a call would not be checked against it");
assert!(!signature.variadic);
}
#[test]
fn the_target_decides_which_type_a_width_names() {
for (triple, written) in [
("x86_64-unknown-linux-gnu", "unsigned long (const char *)"),
("x86_64-pc-windows-msvc", "unsigned long long (const char *)"),
] {
let fixture = Fixture::new(triple);
let mut c = fixture.checker();
let ty = c.signature_type("size_t(const char *)").expect("a type");
assert_eq!(spelled(&c, ty), written, "on {triple}");
}
}
#[test]
fn a_signature_the_reader_cannot_make_sense_of_is_no_type_rather_than_a_wrong_one() {
let fixture = Fixture::new("x86_64-unknown-linux-gnu");
let mut c = fixture.checker();
assert_eq!(c.signature_type("int"), None, "no parameter list at all");
assert_eq!(c.signature_type("int(unsigned int"), None, "unclosed");
assert_eq!(c.signature_type("struct tm *(void)"), None, "a word that is not in the set");
}
#[test]
fn every_signature_in_the_table_builds_a_type() {
let fixture = Fixture::new("x86_64-unknown-linux-gnu");
let mut c = fixture.checker();
let mut built = 0;
for feature in rucc_gnu::features() {
if feature.kind != Kind::Builtin || feature.signature.is_empty() {
continue;
}
let ty = c.signature_type(feature.signature);
assert!(ty.is_some(), "{} has a signature this cannot read", feature.name);
built += 1;
}
assert!(built > 40, "the table lost its signatures, only {built} left");
}
#[test]
fn only_a_name_the_table_has_is_declared() {
let mut fixture = Fixture::new("x86_64-unknown-linux-gnu");
let known = fixture.names.intern("__builtin_clzll");
let ordinary = fixture.names.intern("printf");
let untyped = fixture.names.intern("__builtin_constant_p");
let mut c = fixture.checker();
let decl = c.declare_builtin(known, Span::DUMMY).expect("in the table");
assert_eq!(spelled(&c, c.tast[decl].ty), "int (unsigned long long)");
assert_eq!(c.tast[decl].linkage, Linkage::External);
assert_eq!(c.tast[decl].state, Definition::Declared);
assert_eq!(c.declare_builtin(ordinary, Span::DUMMY), None);
assert_eq!(c.declare_builtin(untyped, Span::DUMMY), None);
}
#[test]
fn a_builtin_first_called_inside_a_block_is_still_declared_at_the_file_scope() {
let mut fixture = Fixture::new("x86_64-unknown-linux-gnu");
let name = fixture.names.intern("__builtin_trap");
let mut c = fixture.checker();
c.scopes.push();
c.scopes.push();
let decl = c.declare_builtin(name, Span::DUMMY).expect("in the table");
c.scopes.pop();
c.scopes.pop();
assert_eq!(c.scopes.lookup(name), Some(Binding::Decl(decl)));
assert!(c.tast.top_level().is_empty(), "the program declared nothing");
}
}