use rucc_ast::{AlignSpec, AttrArg, AttrList};
use rucc_diag::{Diagnostic, Span};
use rucc_types::{TypeId, TypeKind, is_arithmetic, layout};
use crate::check::Checker;
use crate::eval;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(in crate::check) struct Packing {
pub(in crate::check) packed: bool,
pub(in crate::check) align: Option<u32>,
}
const BIGGEST_ALIGNMENT: u32 = 16;
const RETAINING: [&str; 5] = ["used", "retain", "constructor", "destructor", "alias"];
impl Checker<'_> {
pub(in crate::check) fn packing(&mut self, attrs: AttrList) -> Packing {
let mut packing = Packing::default();
let written = self.ast[attrs].to_vec();
for attr in written {
if attr.namespace.is_some_and(|ns| self.text(ns) != "gnu") {
continue;
}
match rucc_gnu::unarmour(self.text(attr.name)) {
"packed" => packing.packed = true,
"aligned" => {
if let Some(align) = self.aligned_argument(attr) {
packing.align = Some(packing.align.unwrap_or(1).max(align));
}
}
"scalar_storage_order" => {
let what = "'scalar_storage_order' is not implemented yet";
let note = "every scalar in this record would be read in the wrong byte order";
let refused = Diagnostic::error(what, attr.span).with_code("E0688");
self.report(refused.note(note, attr.span));
}
_ => {}
}
}
packing
}
pub(in crate::check) fn retains(&mut self, attrs: AttrList) -> bool {
let written = self.ast[attrs].to_vec();
for attr in written {
if attr.namespace.is_some_and(|ns| self.text(ns) != "gnu") {
continue;
}
if RETAINING.contains(&rucc_gnu::unarmour(self.text(attr.name))) {
return true;
}
}
false
}
pub(in crate::check) fn member_alignas(
&mut self,
align: Option<AlignSpec>,
span: Span,
) -> Option<u32> {
let requested = match align? {
AlignSpec::Type(named) => {
let named = self.type_name(named);
i128::from(layout(&self.types, named, self.cx.target).ok()?.align)
}
AlignSpec::Expr(expr) => {
let value = self.expr(expr);
match self.eval_integer(value) {
Ok(value) => value,
Err(failed) => {
if !failed.poisoned {
let at = self.tast.expr_span(failed.at);
let what = "requested alignment is not an integer constant";
self.report(Diagnostic::error(what, at).with_code("E0606"));
}
return None;
}
}
}
};
if requested == 0 {
return None;
}
if requested < 0 || requested & (requested - 1) != 0 {
let what = format!("requested alignment '{requested}' is not a positive power of 2");
self.report(Diagnostic::error(what, span).with_code("E0607"));
return None;
}
u32::try_from(requested).ok()
}
pub(in crate::check) fn vectorized(&mut self, ty: TypeId, attrs: AttrList) -> TypeId {
let written = self.ast[attrs].to_vec();
let mut vector = ty;
for attr in written {
if attr.namespace.is_some_and(|ns| self.text(ns) != "gnu") {
continue;
}
if rucc_gnu::unarmour(self.text(attr.name)) != "vector_size" {
continue;
}
if let Some(made) = self.vector_of(ty, attr) {
vector = made;
}
}
vector
}
fn vector_of(&mut self, elem: TypeId, attr: rucc_ast::Attribute) -> Option<TypeId> {
let args = self.ast[attr.args].to_vec();
let bytes = match args.as_slice() {
[AttrArg::Expr(expr)] => {
let value = self.expr(*expr);
match self.eval_integer(value) {
Ok(value) => value,
Err(failed) => {
if !failed.poisoned {
let at = self.tast.expr_span(failed.at);
let what =
"'vector_size' attribute argument is not an integer constant";
self.report(Diagnostic::error(what, at).with_code("E0689"));
}
return None;
}
}
}
_ => {
let what = "wrong number of arguments specified for 'vector_size' attribute";
self.report(Diagnostic::error(what, attr.span).with_code("E0689"));
return None;
}
};
if bytes < 0 {
let what = format!("'vector_size' attribute argument value '{bytes}' is negative");
self.report(Diagnostic::error(what, attr.span).with_code("E0689"));
return None;
}
let canonical = self.types.canonical(elem);
let boolean = matches!(eval::bare(&self.types, elem), TypeKind::Bool);
if !is_arithmetic(&self.types, canonical) || boolean {
let what = "invalid vector type for attribute 'vector_size'";
let note = "a lane is one of the arithmetic types, and is not a bool";
let refused = Diagnostic::error(what, attr.span).with_code("E0690");
self.report(refused.note(note, attr.span));
return None;
}
let size = layout(&self.types, elem, self.cx.target).ok()?.size;
let bytes = u64::try_from(bytes).ok()?;
if bytes == 0 {
self.report(Diagnostic::error("zero vector size", attr.span).with_code("E0690"));
return None;
}
if size == 0 || bytes % size != 0 {
let what = "vector size not an integral multiple of component size";
let note = format!("one lane is '{size}' bytes, and every lane has to fit");
let refused = Diagnostic::error(what, attr.span).with_code("E0690");
self.report(refused.note(note, attr.span));
return None;
}
let lanes = u32::try_from(bytes / size).ok()?;
if !lanes.is_power_of_two() {
let what = format!("number of vector components {lanes} not a power of two");
let refused = Diagnostic::error(what, attr.span).with_code("E0690");
self.report(refused.note("no machine has such a register", attr.span));
return None;
}
Some(self.types.vector(elem, lanes))
}
fn aligned_argument(&mut self, attr: rucc_ast::Attribute) -> Option<u32> {
let args = self.ast[attr.args].to_vec();
let requested = match args.first() {
None => return Some(BIGGEST_ALIGNMENT),
Some(AttrArg::Expr(expr)) => {
let value = self.expr(*expr);
match self.eval_integer(value) {
Ok(value) => value,
Err(failed) => {
if !failed.poisoned {
let at = self.tast.expr_span(failed.at);
let what = "requested alignment is not an integer constant";
self.report(Diagnostic::error(what, at).with_code("E0606"));
}
return None;
}
}
}
Some(AttrArg::Ident(_)) => {
let what = "requested alignment is not an integer constant";
self.report(Diagnostic::error(what, attr.span).with_code("E0606"));
return None;
}
};
if requested <= 0 || requested & (requested - 1) != 0 {
let what = format!("requested alignment '{requested}' is not a positive power of 2");
self.report(Diagnostic::error(what, attr.span).with_code("E0607"));
return None;
}
u32::try_from(requested).ok()
}
}