use rucc_ast::UnaryOp;
use rucc_diag::{Diagnostic, Span};
use rucc_types::{IntKind, layout, pointee};
use crate::check::Checker;
use crate::expr::{AtomicOp, Category, Expr, ExprId, ExprKind, Ordering, Rmw};
use crate::tast::Const;
const FAMILY: &[(&str, AtomicOp)] = &[
("__atomic_load_n", AtomicOp::Load),
("__atomic_load", AtomicOp::LoadInto),
("__atomic_store_n", AtomicOp::Store),
("__atomic_store", AtomicOp::Store),
("__atomic_thread_fence", AtomicOp::Fence),
("__atomic_compare_exchange_n", AtomicOp::CompareExchange),
("__atomic_compare_exchange", AtomicOp::CompareExchange),
("__sync_bool_compare_and_swap", AtomicOp::SwapBool),
("__sync_val_compare_and_swap", AtomicOp::SwapValue),
("__atomic_exchange_n", AtomicOp::Exchange),
("__atomic_exchange", AtomicOp::ExchangeInto),
("__atomic_test_and_set", AtomicOp::TestAndSet),
("__atomic_clear", AtomicOp::Store),
("__atomic_fetch_add", AtomicOp::Fetch(Rmw::Add)),
("__atomic_fetch_sub", AtomicOp::Fetch(Rmw::Sub)),
("__atomic_add_fetch", AtomicOp::Update(Rmw::Add)),
("__atomic_sub_fetch", AtomicOp::Update(Rmw::Sub)),
("__sync_fetch_and_add", AtomicOp::Fetch(Rmw::Add)),
("__sync_fetch_and_sub", AtomicOp::Fetch(Rmw::Sub)),
("__sync_add_and_fetch", AtomicOp::Update(Rmw::Add)),
("__sync_sub_and_fetch", AtomicOp::Update(Rmw::Sub)),
("__sync_lock_test_and_set", AtomicOp::Exchange),
("__sync_lock_release", AtomicOp::Store),
("__atomic_fetch_and", AtomicOp::Fetch(Rmw::And)),
("__atomic_fetch_nand", AtomicOp::Fetch(Rmw::Nand)),
("__atomic_fetch_or", AtomicOp::Fetch(Rmw::Or)),
("__atomic_fetch_xor", AtomicOp::Fetch(Rmw::Xor)),
("__atomic_and_fetch", AtomicOp::Update(Rmw::And)),
("__atomic_nand_fetch", AtomicOp::Update(Rmw::Nand)),
("__atomic_or_fetch", AtomicOp::Update(Rmw::Or)),
("__atomic_xor_fetch", AtomicOp::Update(Rmw::Xor)),
("__sync_fetch_and_and", AtomicOp::Fetch(Rmw::And)),
("__sync_fetch_and_nand", AtomicOp::Fetch(Rmw::Nand)),
("__sync_fetch_and_or", AtomicOp::Fetch(Rmw::Or)),
("__sync_fetch_and_xor", AtomicOp::Fetch(Rmw::Xor)),
("__sync_and_and_fetch", AtomicOp::Update(Rmw::And)),
("__sync_nand_and_fetch", AtomicOp::Update(Rmw::Nand)),
("__sync_or_and_fetch", AtomicOp::Update(Rmw::Or)),
("__sync_xor_and_fetch", AtomicOp::Update(Rmw::Xor)),
];
const LOCK_TEST_AND_SET: &str = "__sync_lock_test_and_set";
const LOCK_RELEASE: &str = "__sync_lock_release";
const THROUGH_POINTER: &str = "__atomic_compare_exchange";
const STORE_THROUGH_POINTER: &str = "__atomic_store";
const CLEAR: &str = "__atomic_clear";
const SET: i128 = 1;
const SYNCHRONIZE: &str = "__sync_synchronize";
const LOCK_FREE: &[&str] = &["__atomic_always_lock_free", "__atomic_is_lock_free"];
const NUMBERED: &[Ordering] = &[
Ordering::Relaxed,
Ordering::Acquire,
Ordering::Acquire,
Ordering::Release,
Ordering::AcqRel,
Ordering::SeqCst,
];
pub(in crate::check) fn shape(spelled: &str) -> Option<AtomicOp> {
FAMILY.iter().find(|&&(name, _)| name == spelled).map(|&(_, op)| op)
}
fn allowed(op: AtomicOp, order: Ordering) -> bool {
match op {
AtomicOp::Load | AtomicOp::LoadInto => {
matches!(order, Ordering::Relaxed | Ordering::Acquire | Ordering::SeqCst)
}
AtomicOp::Store => {
matches!(order, Ordering::Relaxed | Ordering::Release | Ordering::SeqCst)
}
AtomicOp::Fence
| AtomicOp::CompareExchange
| AtomicOp::SwapBool
| AtomicOp::SwapValue
| AtomicOp::Exchange
| AtomicOp::ExchangeInto
| AtomicOp::TestAndSet
| AtomicOp::Fetch(_)
| AtomicOp::Update(_) => true,
}
}
impl Checker<'_> {
pub(in crate::check) fn atomic_builtin(
&mut self,
op: AtomicOp,
spelled: &str,
args: &[ExprId],
span: Span,
) -> ExprId {
let Some(order) = self.order_of(op, args, spelled) else { return self.poison(span) };
let operands = match op {
AtomicOp::Fence => Vec::new(),
AtomicOp::Load => vec![args[0]],
AtomicOp::LoadInto => vec![args[0], args[1]],
AtomicOp::Store | AtomicOp::Exchange | AtomicOp::Fetch(_) | AtomicOp::Update(_) => {
let target = self.object(spelled, args[0]);
vec![args[0], self.written(spelled, args, target, span)]
}
AtomicOp::ExchangeInto => {
let target = self.accessed(args[0]);
let value = self.value_at(args[1], target, span);
vec![args[0], value, args[2]]
}
AtomicOp::TestAndSet => {
let byte = self.types.int(IntKind::UChar);
vec![args[0], self.constant(Const::Int(SET), byte, span)]
}
AtomicOp::CompareExchange => {
let target = self.accessed(args[0]);
let desired = if spelled == THROUGH_POINTER {
self.value_at(args[2], target, span)
} else {
self.conv().to_type(args[2], target)
};
vec![args[0], args[1], desired]
}
AtomicOp::SwapBool | AtomicOp::SwapValue => {
let target = self.accessed(args[0]);
let expected = self.conv().to_type(args[1], target);
let desired = self.conv().to_type(args[2], target);
vec![args[0], expected, desired]
}
};
let ty = match op {
AtomicOp::Load
| AtomicOp::SwapValue
| AtomicOp::Exchange
| AtomicOp::Fetch(_)
| AtomicOp::Update(_) => self.accessed(args[0]),
AtomicOp::CompareExchange | AtomicOp::SwapBool | AtomicOp::TestAndSet => {
self.types.boolean()
}
AtomicOp::Store | AtomicOp::LoadInto | AtomicOp::ExchangeInto | AtomicOp::Fence => {
self.types.void()
}
};
let args = self.tast.add_expr_refs(&operands);
self.tast.expr(Expr::new(ExprKind::Atomic { op, order, args }, ty, Category::Rvalue), span)
}
fn order_of(&mut self, op: AtomicOp, args: &[ExprId], spelled: &str) -> Option<Ordering> {
if spelled.starts_with("__sync_") {
return Some(match spelled {
LOCK_TEST_AND_SET => Ordering::Acquire,
LOCK_RELEASE => Ordering::Release,
_ => Ordering::SeqCst,
});
}
if op == AtomicOp::CompareExchange {
let [.., success, failure] = args else { return None };
let _ = self.ordering(AtomicOp::Load, *failure, spelled);
return Some(self.ordering(op, *success, spelled));
}
let &written = args.last()?;
Some(self.ordering(op, written, spelled))
}
fn object(&mut self, spelled: &str, pointer: ExprId) -> rucc_types::TypeId {
if spelled == CLEAR {
return self.types.int(IntKind::UChar);
}
self.accessed(pointer)
}
fn written(
&mut self,
spelled: &str,
args: &[ExprId],
target: rucc_types::TypeId,
span: Span,
) -> ExprId {
let value = match spelled {
LOCK_RELEASE | CLEAR => {
let int = self.types.int(IntKind::Int);
self.constant(Const::Int(0), int, span)
}
STORE_THROUGH_POINTER => self.value_at(args[1], target, span),
_ => args[1],
};
self.conv().to_type(value, target)
}
fn value_at(&mut self, pointer: ExprId, target: rucc_types::TypeId, span: Span) -> ExprId {
let node = ExprKind::Unary { op: UnaryOp::Deref, operand: pointer };
let read = self.tast.expr(Expr::new(node, target, Category::Lvalue), span);
self.value(read)
}
pub(in crate::check) fn sync_builtin_value(
&mut self,
function: Option<rucc_base::Symbol>,
span: Span,
) -> Option<ExprId> {
let name = function?;
let spelled = self.text(name);
if !spelled.starts_with("__sync_") || spelled != SYNCHRONIZE {
return None;
}
let ty = self.types.void();
let args = self.tast.add_expr_refs(&[]);
let kind = ExprKind::Atomic { op: AtomicOp::Fence, order: Ordering::SeqCst, args };
Some(self.tast.expr(Expr::new(kind, ty, Category::Rvalue), span))
}
pub(in crate::check) fn lock_free_builtin_value(
&mut self,
function: Option<rucc_base::Symbol>,
args: &[ExprId],
span: Span,
) -> Option<ExprId> {
let name = function?;
let spelled = self.text(name);
if !spelled.starts_with("__atomic_") || !LOCK_FREE.contains(&spelled) {
return None;
}
let &[size, object] = args else { return None };
let widest = u128::from(self.cx.target.lock_free_width / 8);
let bytes = self.folded(size).and_then(|number| u128::try_from(number).ok());
let free = bytes.is_some_and(|bytes| {
bytes.is_power_of_two()
&& bytes <= widest
&& u128::from(self.aligned_to(object)) >= bytes
});
let boolean = self.types.boolean();
Some(self.constant(Const::Int(i128::from(free)), boolean, span))
}
fn folded(&mut self, expr: ExprId) -> Option<i128> {
if self.is_poisoned(expr) {
return None;
}
let mut eval = self.eval();
let folded = eval.constant(expr);
let _ = eval.finish();
match folded {
Ok(Const::Int(number)) => Some(number),
_ => None,
}
}
fn aligned_to(&mut self, object: ExprId) -> u64 {
if self.conv().is_null_pointer_constant(object) {
return u64::MAX;
}
let mut expr = object;
while let ExprKind::Cast(inner) | ExprKind::Convert { operand: inner, .. } =
self.tast[expr].kind
{
expr = inner;
}
let Some(target) = pointee(&self.types, self.tast[expr].ty) else { return u64::MAX };
layout(&self.types, target, self.cx.target).map_or(u64::MAX, |it| it.align)
}
fn accessed(&mut self, object: ExprId) -> rucc_types::TypeId {
match pointee(&self.types, self.tast[object].ty) {
Some(target) => self.plain(target),
None => self.tast[object].ty,
}
}
fn ordering(&mut self, op: AtomicOp, written: ExprId, spelled: &str) -> Ordering {
if self.is_poisoned(written) {
return Ordering::SeqCst;
}
let mut eval = self.eval();
let folded = eval.constant(written);
let _ = eval.finish();
let at = self.tast.expr_span(written);
let Ok(Const::Int(number)) = folded else {
return Ordering::SeqCst;
};
let known = usize::try_from(number).ok().and_then(|index| NUMBERED.get(index).copied());
let Some(order) = known.filter(|&order| allowed(op, order)) else {
self.report(
Diagnostic::warning(
format!(
"{number} is not a memory order '{spelled}' can be given, so this is \
ordered as if it were sequentially consistent"
),
at,
)
.with_code("W0333"),
);
return Ordering::SeqCst;
};
order
}
}
#[cfg(test)]
mod tests {
use rucc_gnu::{Kind, Status};
use super::*;
#[test]
fn the_generic_names_are_rows_of_the_table_that_carry_no_signature() {
for &(name, _) in FAMILY {
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} has a signature and is type generic");
}
}
#[test]
fn the_barrier_of_the_older_family_is_a_row_that_carries_one() {
let feature = rucc_gnu::lookup(Kind::Builtin, SYNCHRONIZE).expect("a row of features.toml");
assert_eq!(feature.status, Status::Implemented);
assert!(!feature.signature.is_empty(), "it is checked against its prototype");
assert!(feature.library.is_empty(), "it is not a call to anything");
}
#[test]
fn the_numbers_are_the_ones_the_headers_use() {
assert_eq!(NUMBERED[0], Ordering::Relaxed);
assert_eq!(NUMBERED[2], Ordering::Acquire);
assert_eq!(NUMBERED[3], Ordering::Release);
assert_eq!(NUMBERED[4], Ordering::AcqRel);
assert_eq!(NUMBERED[5], Ordering::SeqCst);
assert_eq!(NUMBERED.len(), 6);
}
#[test]
fn consume_is_read_as_acquire() {
assert_eq!(NUMBERED[1], Ordering::Acquire);
}
#[test]
fn an_operation_refuses_the_orderings_it_has_nothing_to_say_about() {
assert!(!allowed(AtomicOp::Load, Ordering::Release));
assert!(!allowed(AtomicOp::Load, Ordering::AcqRel));
assert!(!allowed(AtomicOp::Store, Ordering::Acquire));
assert!(!allowed(AtomicOp::Store, Ordering::AcqRel));
}
#[test]
fn every_operation_takes_the_orderings_it_means_something_for() {
assert!(allowed(AtomicOp::Load, Ordering::Relaxed));
assert!(allowed(AtomicOp::Load, Ordering::Acquire));
assert!(allowed(AtomicOp::Load, Ordering::SeqCst));
assert!(allowed(AtomicOp::Store, Ordering::Relaxed));
assert!(allowed(AtomicOp::Store, Ordering::Release));
assert!(allowed(AtomicOp::Store, Ordering::SeqCst));
for &order in NUMBERED {
assert!(allowed(AtomicOp::Fence, order), "a barrier takes {order:?}");
}
}
#[test]
fn the_name_that_takes_its_desired_value_through_a_pointer_is_one_of_the_family() {
assert_eq!(shape(THROUGH_POINTER), Some(AtomicOp::CompareExchange));
assert_eq!(THROUGH_POINTER, "__atomic_compare_exchange");
assert_ne!(
THROUGH_POINTER, "__atomic_compare_exchange_n",
"the suffixed one takes a value"
);
}
#[test]
fn an_exchange_takes_every_ordering_there_is() {
for op in [AtomicOp::CompareExchange, AtomicOp::SwapBool, AtomicOp::SwapValue] {
for &order in NUMBERED {
assert!(allowed(op, order), "an exchange takes {order:?}");
}
}
}
#[test]
fn the_two_questions_are_rows_that_carry_a_signature() {
for &name in LOCK_FREE {
let feature = rucc_gnu::lookup(Kind::Builtin, name).expect("a row of 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!(shape(name).is_none(), "{name} is not one of the type generic ones");
}
}
#[test]
fn a_name_outside_the_family_asks_for_nothing() {
assert_eq!(shape("__atomic_load_n"), Some(AtomicOp::Load));
assert_eq!(shape("__atomic_store_n"), Some(AtomicOp::Store));
assert_eq!(shape("__atomic_thread_fence"), Some(AtomicOp::Fence));
assert_eq!(shape("__atomic_compare_exchange_n"), Some(AtomicOp::CompareExchange));
assert_eq!(shape("__atomic_compare_exchange"), Some(AtomicOp::CompareExchange));
assert_eq!(shape("__sync_bool_compare_and_swap"), Some(AtomicOp::SwapBool));
assert_eq!(shape("__sync_val_compare_and_swap"), Some(AtomicOp::SwapValue));
assert_eq!(shape("__atomic_exchange_n"), Some(AtomicOp::Exchange));
assert_eq!(shape(LOCK_TEST_AND_SET), Some(AtomicOp::Exchange));
assert_eq!(shape(LOCK_RELEASE), Some(AtomicOp::Store));
assert_eq!(shape("__atomic_fetch_add"), Some(AtomicOp::Fetch(Rmw::Add)));
assert_eq!(shape("__atomic_sub_fetch"), Some(AtomicOp::Update(Rmw::Sub)));
assert_eq!(shape("__sync_fetch_and_sub"), Some(AtomicOp::Fetch(Rmw::Sub)));
assert_eq!(shape("__sync_add_and_fetch"), Some(AtomicOp::Update(Rmw::Add)));
assert_eq!(shape("__atomic_fetch_and"), Some(AtomicOp::Fetch(Rmw::And)));
assert_eq!(shape("__atomic_nand_fetch"), Some(AtomicOp::Update(Rmw::Nand)));
assert_eq!(shape("__sync_fetch_and_or"), Some(AtomicOp::Fetch(Rmw::Or)));
assert_eq!(shape("__sync_xor_and_fetch"), Some(AtomicOp::Update(Rmw::Xor)));
assert_eq!(shape("__atomic_load"), Some(AtomicOp::LoadInto));
assert_eq!(shape(STORE_THROUGH_POINTER), Some(AtomicOp::Store));
assert_eq!(shape("__atomic_exchange"), Some(AtomicOp::ExchangeInto));
assert_eq!(shape("__atomic_test_and_set"), Some(AtomicOp::TestAndSet));
assert_eq!(shape(CLEAR), Some(AtomicOp::Store));
assert_eq!(shape("__atomic_signal_fence"), None);
assert_eq!(shape(SYNCHRONIZE), None);
}
}