use rucc_diag::{Diagnostic, Span};
use rucc_types::pointee;
use crate::check::Checker;
use crate::expr::{AtomicOp, Category, Expr, ExprId, ExprKind, Ordering};
use crate::tast::Const;
const FAMILY: &[(&str, AtomicOp)] = &[
("__atomic_load_n", AtomicOp::Load),
("__atomic_store_n", AtomicOp::Store),
("__atomic_thread_fence", AtomicOp::Fence),
];
const SYNCHRONIZE: &str = "__sync_synchronize";
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 => matches!(order, Ordering::Relaxed | Ordering::Acquire | Ordering::SeqCst),
AtomicOp::Store => {
matches!(order, Ordering::Relaxed | Ordering::Release | Ordering::SeqCst)
}
AtomicOp::Fence => true,
}
}
impl Checker<'_> {
pub(in crate::check) fn atomic_builtin(
&mut self,
op: AtomicOp,
spelled: &str,
args: &[ExprId],
span: Span,
) -> ExprId {
let Some(&written) = args.last() else { return self.poison(span) };
let order = self.ordering(op, written, spelled);
let operands = match op {
AtomicOp::Fence => Vec::new(),
AtomicOp::Load => vec![args[0]],
AtomicOp::Store => {
let target = self.accessed(args[0]);
vec![args[0], self.conv().to_type(args[1], target)]
}
};
let ty = match op {
AtomicOp::Load => self.accessed(args[0]),
AtomicOp::Store | 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)
}
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))
}
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 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_load"), None);
assert_eq!(shape("__atomic_store"), None);
assert_eq!(shape("__atomic_signal_fence"), None);
assert_eq!(shape("__atomic_fetch_add"), None);
assert_eq!(shape(SYNCHRONIZE), None);
}
}