use rucc_ast as ast;
use rucc_base::Symbol;
use rucc_diag::{Diagnostic, Span};
use rucc_types::{
FunctionType, IntKind, TypeId, TypeKind, is_integer, is_pointer, is_void, pointee,
};
use crate::check::Checker;
use crate::decl::{Decl, DeclKind, DeclList, Definition, Linkage, StorageDuration};
use crate::expr::{Category, Expr, ExprId, ExprKind};
use crate::tast::{Base, Const};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Param {
Object,
Value,
Place,
Order,
Weak,
Integer,
Out,
Any,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Answer {
Pointee,
Void,
Bool,
Int,
}
#[derive(Debug, Clone, Copy)]
struct Generic {
name: &'static str,
params: &'static [Param],
answer: Answer,
trailing: bool,
}
impl Generic {
fn needs_pointee(&self) -> bool {
self.answer == Answer::Pointee
|| self.params.iter().any(|param| matches!(param, Param::Value | Param::Place))
}
}
const RMW: &[Param] = &[Param::Object, Param::Value, Param::Order];
const OVERFLOW: &[Param] = &[Param::Integer, Param::Integer, Param::Out];
const SYNC: &[Param] = &[Param::Object, Param::Value];
const fn row(name: &'static str, params: &'static [Param], answer: Answer) -> Generic {
Generic { name, params, answer, trailing: false }
}
const fn sync(name: &'static str, params: &'static [Param], answer: Answer) -> Generic {
Generic { name, params, answer, trailing: true }
}
const GENERIC: &[Generic] = &[
row("__atomic_load_n", &[Param::Object, Param::Order], Answer::Pointee),
row("__atomic_load", &[Param::Object, Param::Place, Param::Order], Answer::Void),
row("__atomic_store_n", RMW, Answer::Void),
row("__atomic_store", &[Param::Object, Param::Place, Param::Order], Answer::Void),
row("__atomic_exchange_n", RMW, Answer::Pointee),
row(
"__atomic_exchange",
&[Param::Object, Param::Place, Param::Place, Param::Order],
Answer::Void,
),
row(
"__atomic_compare_exchange_n",
&[Param::Object, Param::Place, Param::Value, Param::Weak, Param::Order, Param::Order],
Answer::Bool,
),
row(
"__atomic_compare_exchange",
&[Param::Object, Param::Place, Param::Place, Param::Weak, Param::Order, Param::Order],
Answer::Bool,
),
row("__atomic_fetch_add", RMW, Answer::Pointee),
row("__atomic_fetch_sub", RMW, Answer::Pointee),
row("__atomic_fetch_and", RMW, Answer::Pointee),
row("__atomic_fetch_or", RMW, Answer::Pointee),
row("__atomic_fetch_xor", RMW, Answer::Pointee),
row("__atomic_fetch_nand", RMW, Answer::Pointee),
row("__atomic_add_fetch", RMW, Answer::Pointee),
row("__atomic_sub_fetch", RMW, Answer::Pointee),
row("__atomic_and_fetch", RMW, Answer::Pointee),
row("__atomic_or_fetch", RMW, Answer::Pointee),
row("__atomic_xor_fetch", RMW, Answer::Pointee),
row("__atomic_nand_fetch", RMW, Answer::Pointee),
row("__atomic_test_and_set", &[Param::Object, Param::Order], Answer::Bool),
row("__atomic_clear", &[Param::Object, Param::Order], Answer::Void),
row("__atomic_thread_fence", &[Param::Order], Answer::Void),
row("__atomic_signal_fence", &[Param::Order], Answer::Void),
sync("__sync_fetch_and_add", SYNC, Answer::Pointee),
sync("__sync_fetch_and_sub", SYNC, Answer::Pointee),
sync("__sync_fetch_and_or", SYNC, Answer::Pointee),
sync("__sync_fetch_and_and", SYNC, Answer::Pointee),
sync("__sync_fetch_and_xor", SYNC, Answer::Pointee),
sync("__sync_fetch_and_nand", SYNC, Answer::Pointee),
sync("__sync_add_and_fetch", SYNC, Answer::Pointee),
sync("__sync_sub_and_fetch", SYNC, Answer::Pointee),
sync("__sync_or_and_fetch", SYNC, Answer::Pointee),
sync("__sync_and_and_fetch", SYNC, Answer::Pointee),
sync("__sync_xor_and_fetch", SYNC, Answer::Pointee),
sync("__sync_nand_and_fetch", SYNC, Answer::Pointee),
sync(
"__sync_bool_compare_and_swap",
&[Param::Object, Param::Value, Param::Value],
Answer::Bool,
),
sync(
"__sync_val_compare_and_swap",
&[Param::Object, Param::Value, Param::Value],
Answer::Pointee,
),
sync("__sync_lock_test_and_set", SYNC, Answer::Pointee),
sync("__sync_lock_release", &[Param::Object], Answer::Void),
row("__builtin_add_overflow", OVERFLOW, Answer::Bool),
row("__builtin_sub_overflow", OVERFLOW, Answer::Bool),
row("__builtin_mul_overflow", OVERFLOW, Answer::Bool),
row(CONSTANT_P, &[Param::Any], Answer::Int),
row("__builtin_classify_type", &[Param::Any], Answer::Int),
];
const CONSTANT_P: &str = "__builtin_constant_p";
impl Checker<'_> {
pub(in crate::check) fn generic_builtin_call(
&mut self,
name: Symbol,
args: ast::ExprList,
span: Span,
) -> Option<ExprId> {
let spelled = self.text(name);
if !spelled.starts_with("__") {
return None;
}
let generic = *GENERIC.iter().find(|row| row.name == spelled)?;
if self.scopes.lookup(name).is_some() {
return None;
}
if generic.name == CONSTANT_P {
return Some(self.constant_p(args, span));
}
Some(self.generic_call(name, generic, args, span))
}
fn constant_p(&mut self, args: ast::ExprList, span: Span) -> ExprId {
let written: Vec<ast::ExprId> = self.ast[args].to_vec();
let [written] = written[..] else {
let how = if written.is_empty() { "few" } else { "many" };
self.report(
Diagnostic::error(format!("too {how} arguments to function '{CONSTANT_P}'"), span)
.with_code("E0511"),
);
return self.poison(span);
};
let arg = self.expr(written);
let arg = self.value(arg);
let answer = !self.is_poisoned(arg) && self.folds(arg);
let int = self.int();
self.constant(Const::Int(i128::from(answer)), int, span)
}
fn folds(&mut self, arg: ExprId) -> bool {
let mut eval = self.eval();
let value = eval.constant(arg);
let _ = eval.finish();
match value {
Ok(Const::Int(_) | Const::Float(_)) => true,
Ok(Const::Address(address)) => matches!(address.base, Base::Str(_)),
Err(_) => false,
}
}
fn generic_call(
&mut self,
name: Symbol,
generic: Generic,
args: ast::ExprList,
span: Span,
) -> ExprId {
let written: Vec<ast::ExprId> = self.ast[args].to_vec();
let checked: Vec<ExprId> = written
.into_iter()
.map(|arg| {
let arg = self.expr(arg);
self.value(arg)
})
.collect();
let spelled = self.text(name).to_owned();
let wanted = generic.params.len();
let given = checked.len();
if given < wanted || (given > wanted && !generic.trailing) {
let how = if given < wanted { "few" } else { "many" };
self.report(
Diagnostic::error(format!("too {how} arguments to function '{spelled}'"), span)
.with_code("E0511"),
);
return self.poison(span);
}
let Some(target) = self.object_type(&generic, &checked, &spelled) else {
return self.poison(span);
};
let mut params = Vec::with_capacity(wanted);
let mut wrong = false;
for (index, ¶m) in generic.params.iter().enumerate() {
let arg = checked[index];
let ty = self.tast[arg].ty;
if !self.is_poisoned(arg) && !self.argument_fits(param, arg, index, &spelled) {
wrong = true;
}
params.push(match param {
Param::Value => target.unwrap_or(ty),
Param::Place => match target {
Some(target) => self.types.pointer(target),
None => ty,
},
Param::Order => self.types.int(IntKind::Int),
Param::Weak => self.types.boolean(),
Param::Object | Param::Integer | Param::Out | Param::Any => ty,
});
}
if wrong {
return self.poison(span);
}
let ret = match generic.answer {
Answer::Pointee => target.unwrap_or_else(|| self.types.void()),
Answer::Void => self.types.void(),
Answer::Bool => self.types.boolean(),
Answer::Int => self.types.int(IntKind::Int),
};
let ty = self.types.function(FunctionType {
ret,
params,
variadic: generic.trailing,
prototyped: true,
});
let decl = self.tast.decl(
Decl {
name: Some(name),
ty,
kind: DeclKind::Function,
linkage: Linkage::External,
duration: StorageDuration::Static,
state: Definition::Declared,
alignment: None,
constant: false,
init: None,
params: DeclList::EMPTY,
body: None,
},
span,
);
let callee = self.tast.expr(Expr::new(ExprKind::Decl(decl), ty, Category::Function), span);
let callee = self.value(callee);
self.finish_call(callee, Some(name), checked, span)
}
fn object_type(
&mut self,
generic: &Generic,
checked: &[ExprId],
spelled: &str,
) -> Option<Option<TypeId>> {
if generic.params.first() != Some(&Param::Object) {
return Some(None);
}
let arg = checked[0];
if self.is_poisoned(arg) {
return None;
}
let ty = self.tast[arg].ty;
let at = self.tast.expr_span(arg);
let target = pointee(&self.types, ty)
.filter(|&target| !generic.needs_pointee() || !is_void(&self.types, target));
let Some(target) = target else {
self.report(
Diagnostic::error(
format!("argument 1 of '{spelled}' must be a non-void pointer type"),
at,
)
.with_code("E0670"),
);
return None;
};
Some(Some(self.plain(target)))
}
fn argument_fits(&mut self, param: Param, arg: ExprId, index: usize, spelled: &str) -> bool {
let ty = self.tast[arg].ty;
let at = self.tast.expr_span(arg);
let number = index + 1;
match param {
Param::Place if !is_pointer(&self.types, ty) => {
self.report(
Diagnostic::error(
format!("argument {number} of '{spelled}' must be a pointer type"),
at,
)
.with_code("E0670"),
);
false
}
Param::Integer if !is_integer(&self.types, ty) => {
self.report(
Diagnostic::error(
format!(
"argument {number} in call to function '{spelled}' does not have \
integral type"
),
at,
)
.with_code("E0671"),
);
false
}
Param::Out
if !pointee(&self.types, ty)
.is_some_and(|target| is_integer(&self.types, target)) =>
{
self.report(
Diagnostic::error(
format!(
"argument {number} in call to function '{spelled}' does not have \
pointer to integral type"
),
at,
)
.with_code("E0671"),
);
false
}
_ => true,
}
}
fn plain(&mut self, ty: TypeId) -> TypeId {
let inner = match self.types.kind(self.types.canonical(ty)) {
TypeKind::Atomic(inner) => inner,
_ => ty,
};
self.types.unqualified(inner)
}
}
#[cfg(test)]
mod tests {
use rucc_gnu::Kind;
use super::*;
#[test]
fn every_rule_is_a_row_of_the_table_and_every_row_without_a_signature_has_a_rule() {
for generic in GENERIC {
let feature = rucc_gnu::lookup(Kind::Builtin, generic.name);
assert!(feature.is_some(), "{} has a rule and is not in features.toml", generic.name);
}
let syntax = [
"__builtin_types_compatible_p",
"__builtin_choose_expr",
"__builtin_offsetof",
"__builtin_va_list",
"__builtin_va_start",
"__builtin_va_arg",
"__builtin_va_end",
"__builtin_va_copy",
];
for feature in rucc_gnu::features() {
if feature.kind != Kind::Builtin || !feature.signature.is_empty() {
continue;
}
let known = GENERIC.iter().any(|generic| generic.name == feature.name)
|| syntax.contains(&feature.name);
assert!(known, "{} has neither a signature nor a rule", feature.name);
}
}
#[test]
fn a_rule_that_uses_the_object_asks_for_one_first() {
for generic in GENERIC {
let uses = generic.needs_pointee();
let has = generic.params.first() == Some(&Param::Object);
assert!(!uses || has, "{} uses what it was not given", generic.name);
let others = generic.params.iter().skip(1).filter(|p| **p == Param::Object).count();
assert_eq!(others, 0, "{} names an object twice", generic.name);
}
}
}