use rucc_ast::{AlignSpec, AttrArg, AttrList};
use rucc_base::float::Format;
use rucc_diag::{Diagnostic, Span};
use rucc_lex::Encoding;
use rucc_target::TargetInfo;
use rucc_types::{
FloatKind, IntKind, TypeId, TypeKind, float_format, int_width, integer_info, is_arithmetic,
is_complex, is_real_floating, layout,
};
use crate::check::Checker;
use crate::decl::Visibility;
use crate::eval;
use crate::expr::ExprKind;
use crate::tast::StrId;
#[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"];
const FLOATS: [FloatKind; 5] = [
FloatKind::Float16,
FloatKind::Float,
FloatKind::Double,
FloatKind::LongDouble,
FloatKind::Float128,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Int(u32),
Float(Format),
Complex(Format),
}
fn named_mode(name: &str, target: &TargetInfo) -> Option<Mode> {
let mode = match name {
"QI" => Mode::Int(8),
"HI" => Mode::Int(16),
"SI" => Mode::Int(32),
"DI" => Mode::Int(64),
"TI" => Mode::Int(128),
"byte" => Mode::Int(8),
"word" | "unwind_word" | "pointer" => Mode::Int(target.pointer_width),
"HF" => Mode::Float(Format::Half),
"SF" => Mode::Float(Format::Single),
"DF" => Mode::Float(Format::Double),
"XF" => Mode::Float(Format::X87Extended),
"TF" => Mode::Float(Format::Quad),
"HC" => Mode::Complex(Format::Half),
"SC" => Mode::Complex(Format::Single),
"DC" => Mode::Complex(Format::Double),
"XC" => Mode::Complex(Format::X87Extended),
"TC" => Mode::Complex(Format::Quad),
_ => return None,
};
Some(mode)
}
fn is_vector_mode(name: &str, target: &TargetInfo) -> bool {
let Some(rest) = name.strip_prefix('V') else {
return false;
};
let lanes = rest.len() - rest.trim_start_matches(|c: char| c.is_ascii_digit()).len();
lanes > 0 && named_mode(&rest[lanes..], target).is_some()
}
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 aliased(&mut self, attrs: AttrList) -> Option<StrId> {
let written = self.ast[attrs].to_vec();
for attr in written {
if attr.namespace.is_some_and(|ns| self.text(ns) != "gnu") {
continue;
}
if rucc_gnu::unarmour(self.text(attr.name)) == "alias" {
return self.alias_argument(attr);
}
}
None
}
fn alias_argument(&mut self, attr: rucc_ast::Attribute) -> Option<StrId> {
let args = self.ast[attr.args].to_vec();
let what = "'alias' requires a string naming the symbol to alias";
let expr = match args.first() {
Some(AttrArg::Expr(expr)) => *expr,
None | Some(AttrArg::Ident(_)) => {
self.report(Diagnostic::error(what, attr.span).with_code("E0695"));
return None;
}
};
let checked = self.expr(expr);
let ExprKind::Str(id) = self.tast[checked].kind else {
let at = self.tast.expr_span(checked);
self.report(Diagnostic::error(what, at).with_code("E0695"));
return None;
};
if self.tast[id].encoding != Encoding::Plain {
let wide = "wide string literal in 'alias'";
self.report(Diagnostic::error(wide, attr.span).with_code("E0696"));
return None;
}
Some(id)
}
pub(in crate::check) fn seen(&mut self, attrs: AttrList) -> Option<Visibility> {
let written = self.ast[attrs].to_vec();
for attr in written {
if attr.namespace.is_some_and(|ns| self.text(ns) != "gnu") {
continue;
}
if rucc_gnu::unarmour(self.text(attr.name)) == "visibility" {
return self.visibility_argument(attr);
}
}
None
}
fn visibility_argument(&mut self, attr: rucc_ast::Attribute) -> Option<Visibility> {
let args = self.ast[attr.args].to_vec();
let what = "'visibility' requires a string, which is default, hidden, internal or \
protected";
let expr = match args.first() {
Some(AttrArg::Expr(expr)) => *expr,
None | Some(AttrArg::Ident(_)) => {
self.report(Diagnostic::error(what, attr.span).with_code("E0701"));
return None;
}
};
let checked = self.expr(expr);
let ExprKind::Str(id) = self.tast[checked].kind else {
let at = self.tast.expr_span(checked);
self.report(Diagnostic::error(what, at).with_code("E0701"));
return None;
};
if self.tast[id].encoding != Encoding::Plain {
let wide = "wide string literal in 'visibility'";
self.report(Diagnostic::error(wide, attr.span).with_code("E0701"));
return None;
}
let written: String =
self.tast[id].elements.iter().filter_map(|&element| char::from_u32(element)).collect();
match written.as_str() {
"default" => Some(Visibility::Default),
"hidden" | "internal" => Some(Visibility::Hidden),
"protected" => Some(Visibility::Protected),
_ => {
self.report(Diagnostic::error(what, attr.span).with_code("E0701"));
None
}
}
}
pub(in crate::check) fn gnu_inlined(&self, attrs: AttrList) -> bool {
self.ast[attrs].iter().any(|attr| {
!attr.namespace.is_some_and(|ns| self.text(ns) != "gnu")
&& rucc_gnu::unarmour(self.text(attr.name)) == "gnu_inline"
})
}
pub(in crate::check) fn never_returns(&self, attrs: AttrList) -> bool {
self.ast[attrs].iter().any(|attr| {
!attr.namespace.is_some_and(|ns| self.text(ns) != "gnu")
&& rucc_gnu::unarmour(self.text(attr.name)) == "noreturn"
})
}
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 retyped(&mut self, ty: TypeId, attrs: AttrList) -> TypeId {
let ty = self.moded(ty, attrs);
self.vectorized(ty, attrs)
}
fn moded(&mut self, ty: TypeId, attrs: AttrList) -> TypeId {
let written = self.ast[attrs].to_vec();
let mut moded = 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)) != "mode" {
continue;
}
if let Some(made) = self.mode_of(ty, attr) {
moded = made;
}
}
moded
}
fn mode_of(&mut self, ty: TypeId, attr: rucc_ast::Attribute) -> Option<TypeId> {
let args = self.ast[attr.args].to_vec();
let [AttrArg::Ident(named)] = args.as_slice() else {
return None;
};
let target = self.cx.target;
let name = rucc_gnu::unarmour(self.text(*named)).to_string();
let Some(mode) = named_mode(&name, target) else {
if is_vector_mode(&name, target) {
let what = format!("vector machine mode '{name}' is not implemented yet");
let note = "use 'vector_size' instead, which builds the same type";
let refused = Diagnostic::error(what, attr.span).with_code("E0699");
self.report(refused.note(note, attr.span));
return None;
}
let what = format!("unknown machine mode '{name}'");
self.report(Diagnostic::error(what, attr.span).with_code("E0698"));
return None;
};
let inappropriate = format!("mode '{name}' applied to inappropriate type");
let made = match mode {
Mode::Int(bits) => {
let Some(shape) = integer_info(&self.types, ty, target) else {
self.report(Diagnostic::error(inappropriate, attr.span).with_code("E0698"));
return None;
};
let kind = IntKind::ALL.into_iter().find(|&kind| {
kind != IntKind::Char
&& int_width(kind, target) == bits
&& kind.is_signed(target.char_is_signed) == shape.signed
})?;
self.types.int(kind)
}
Mode::Float(format) => {
if !is_real_floating(&self.types, ty) {
self.report(Diagnostic::error(inappropriate, attr.span).with_code("E0698"));
return None;
}
let kind = self.float_in(format, &name, attr.span)?;
self.types.float(kind)
}
Mode::Complex(format) => {
if !is_complex(&self.types, ty) {
self.report(Diagnostic::error(inappropriate, attr.span).with_code("E0698"));
return None;
}
let kind = self.float_in(format, &name, attr.span)?;
self.types.complex(kind)
}
};
Some(made)
}
fn float_in(&mut self, format: Format, name: &str, span: Span) -> Option<FloatKind> {
let target = self.cx.target;
let found = FLOATS.into_iter().find(|&kind| float_format(kind, target) == format);
if found.is_none() {
let what = format!("no data type for mode '{name}'");
self.report(Diagnostic::error(what, span).with_code("E0698"));
}
found
}
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()
}
}