use super::Sema;
use crate::ast;
use crate::capture::SourceRange;
use crate::ir::{Callee, Expr, ExprKind, FuncId, Place, PlaceKind, Ty};
use crate::target::Arch;
pub(super) const LONG_DOUBLE_TWINS: &[(&str, &str)] = &[
("strtold", "strtod"),
("wcstold", "wcstod"),
("acosl", "acos"),
("asinl", "asin"),
("atanl", "atan"),
("atan2l", "atan2"),
("cosl", "cos"),
("sinl", "sin"),
("tanl", "tan"),
("acoshl", "acosh"),
("asinhl", "asinh"),
("atanhl", "atanh"),
("coshl", "cosh"),
("sinhl", "sinh"),
("tanhl", "tanh"),
("expl", "exp"),
("exp2l", "exp2"),
("expm1l", "expm1"),
("frexpl", "frexp"),
("ilogbl", "ilogb"),
("ldexpl", "ldexp"),
("logl", "log"),
("log10l", "log10"),
("log1pl", "log1p"),
("log2l", "log2"),
("logbl", "logb"),
("modfl", "modf"),
("scalbnl", "scalbn"),
("scalblnl", "scalbln"),
("cbrtl", "cbrt"),
("fabsl", "fabs"),
("hypotl", "hypot"),
("powl", "pow"),
("sqrtl", "sqrt"),
("erfl", "erf"),
("erfcl", "erfc"),
("lgammal", "lgamma"),
("tgammal", "tgamma"),
("ceill", "ceil"),
("floorl", "floor"),
("nearbyintl", "nearbyint"),
("rintl", "rint"),
("lrintl", "lrint"),
("llrintl", "llrint"),
("roundl", "round"),
("lroundl", "lround"),
("llroundl", "llround"),
("truncl", "trunc"),
("fmodl", "fmod"),
("remainderl", "remainder"),
("remquol", "remquo"),
("copysignl", "copysign"),
("nanl", "nan"),
("nextafterl", "nextafter"),
("nexttoward", "nextafter"),
("nexttowardf", "nextafterf"),
("nexttowardl", "nextafter"),
("fdiml", "fdim"),
("fmaxl", "fmax"),
("fminl", "fmin"),
("fmal", "fma"),
("cabsl", "cabs"),
("cacosl", "cacos"),
("cacoshl", "cacosh"),
("cargl", "carg"),
("casinl", "casin"),
("casinhl", "casinh"),
("catanl", "catan"),
("catanhl", "catanh"),
("ccosl", "ccos"),
("ccoshl", "ccosh"),
("cexpl", "cexp"),
("cimagl", "cimag"),
("clogl", "clog"),
("conjl", "conj"),
("cpowl", "cpow"),
("cprojl", "cproj"),
("creall", "creal"),
("csinl", "csin"),
("csinhl", "csinh"),
("csqrtl", "csqrt"),
("ctanl", "ctan"),
("ctanhl", "ctanh"),
];
pub fn long_double_twin(name: &str) -> Option<&'static str> {
let lookup = |name: &str| {
LONG_DOUBLE_TWINS
.iter()
.find(|(ld, _)| *ld == name)
.map(|(_, twin)| *twin)
};
if let Some(twin) = lookup(name) {
return Some(twin);
}
match name {
"strtof64x" => Some("strtod"),
"wcstof64x" => Some("wcstod"),
_ => lookup(&format!("{}l", name.strip_suffix("f64x")?)),
}
}
#[derive(Clone, Copy, Default, Debug)]
pub(super) struct LongDoubleSig {
pub ret: Option<u8>,
pub offence: Option<&'static str>,
}
#[derive(Clone, Debug)]
pub(super) enum LongDoubleUse {
Function(FuncId, SourceRange),
Variadic {
func: FuncId,
range: SourceRange,
pointer: bool,
},
}
impl Sema<'_> {
pub(super) fn long_double_depth(&self, ty: &ast::Type) -> Option<u8> {
match &ty.kind {
ast::TypeKind::Float(ast::FloatSize::LongDouble | ast::FloatSize::Float64x)
| ast::TypeKind::Complex(ast::FloatSize::LongDouble | ast::FloatSize::Float64x) => {
Some(0)
}
ast::TypeKind::Pointer(inner) => self.long_double_depth(inner)?.checked_add(1),
ast::TypeKind::Array { elem, .. } => self.long_double_depth(elem)?.checked_add(1),
ast::TypeKind::Typedef(name) => self
.lookup_typedef(name)
.and_then(|entry| self.long_double_decls.get(&entry.range).copied()),
_ => None,
}
}
pub(super) fn note_long_double(&mut self, range: SourceRange, ty: &ast::Type) {
if let Some(depth) = self.long_double_depth(ty) {
self.long_double_decls.insert(range, depth);
}
}
pub(super) fn note_long_double_function(&mut self, id: FuncId, func: &ast::FunctionType) {
let ret = self.long_double_depth(&func.ret);
let offence = match ret {
Some(0) => Some("returns a 'long double'"),
Some(1) => Some("returns a 'long double *'"),
_ => None,
}
.or_else(|| {
func.params
.iter()
.find_map(|param| match self.long_double_depth(¶m.ty) {
Some(0) => Some("takes a 'long double'"),
Some(1) => Some("takes a 'long double *'"),
_ => None,
})
});
if ret.is_none() && offence.is_none() {
return;
}
let entry = self.long_double_funcs.entry(id).or_default();
entry.ret = entry.ret.or(ret);
entry.offence = entry.offence.or(offence);
}
pub(super) fn note_long_double_expr(&mut self, result: &Expr, depth: Option<u8>) {
if depth.is_some() || self.long_double_depth_of(result).is_some() {
self.long_double_exprs.insert(result.range, depth);
}
}
pub(super) fn long_double_depth_of(&self, expr: &Expr) -> Option<u8> {
let depth = match self.long_double_exprs.get(&expr.range) {
Some(depth) => *depth,
None => self.long_double_depth_walk(expr),
}?;
let plausible = match depth {
0 => matches!(expr.ty, Ty::Double | Ty::ComplexDouble),
_ => expr.ty.is_pointer() || matches!(expr.ty, Ty::Array(_)),
};
plausible.then_some(depth)
}
fn long_double_depth_walk(&self, expr: &Expr) -> Option<u8> {
match &expr.kind {
ExprKind::Load(place)
| ExprKind::Assign { place, .. }
| ExprKind::CompoundAssign { place, .. }
| ExprKind::IncDec { place, .. } => self.place_long_double_depth(place),
ExprKind::AddrOf(place) => {
let depth = self.place_long_double_depth(place)?;
if matches!(place.ty, Ty::Array(_)) {
Some(depth)
} else {
depth.checked_add(1)
}
}
ExprKind::Neg(inner) => self.long_double_depth_of(inner),
ExprKind::Binary { lhs, rhs, .. } => {
let zero = |e: &Expr| self.long_double_depth_of(e) == Some(0);
(zero(lhs) || zero(rhs)).then_some(0)
}
ExprKind::PtrOffset { ptr, .. } => self.long_double_depth_of(ptr),
ExprKind::Cast(inner) if inner.ty == expr.ty => self.long_double_depth_of(inner),
ExprKind::Cond {
then_expr,
else_expr,
..
} => self
.long_double_depth_of(then_expr)
.or_else(|| self.long_double_depth_of(else_expr)),
ExprKind::CondDefault { value, else_expr } => self
.long_double_depth_of(value)
.or_else(|| self.long_double_depth_of(else_expr)),
ExprKind::Comma { rhs, .. } => self.long_double_depth_of(rhs),
ExprKind::StmtExpr {
value: Some(value), ..
} => self.long_double_depth_of(value),
ExprKind::Call {
callee: Callee::Direct(id),
..
} => self.long_double_funcs.get(id).and_then(|sig| sig.ret),
_ => None,
}
}
fn place_long_double_depth(&self, place: &Place) -> Option<u8> {
match &place.kind {
PlaceKind::Object(id) => self
.long_double_decls
.get(&self.program.object(*id).range)
.copied(),
PlaceKind::Deref(ptr) | PlaceKind::Index { base: ptr, .. } => {
self.long_double_depth_of(ptr)?.checked_sub(1)
}
PlaceKind::Field { record, index, .. } => {
let field = self.types().record(*record).fields.get(*index)?;
self.long_double_decls.get(&field.range).copied()
}
PlaceKind::ComplexPart { base, .. } => self.place_long_double_depth(base),
PlaceKind::Temporary(value) => self.long_double_depth_of(value),
PlaceKind::Str(_) | PlaceKind::CompoundLiteral { .. } => None,
}
}
pub(super) fn note_long_double_call(
&mut self,
target: &Callee,
callee_range: SourceRange,
variadic_args: &[(SourceRange, Option<u8>)],
) {
let Callee::Direct(id) = target else {
return;
};
if self.dead_code > 0 {
return;
}
self.note_long_double_function_use(*id, callee_range);
for (range, depth) in variadic_args {
if let Some(depth @ (0 | 1)) = depth {
self.long_double_uses.push(LongDoubleUse::Variadic {
func: *id,
range: *range,
pointer: *depth == 1,
});
}
}
}
pub(super) fn note_long_double_function_use(&mut self, id: FuncId, range: SourceRange) {
if self.dead_code == 0
&& self
.long_double_funcs
.get(&id)
.is_some_and(|sig| sig.offence.is_some())
{
self.long_double_uses
.push(LongDoubleUse::Function(id, range));
}
}
fn is_platform_function(&self, id: FuncId) -> bool {
let func = self.program.function(id);
func.body.is_none()
&& func.intrinsic.is_none()
&& !self.defined_functions.contains(&func.name)
}
pub(super) fn check_long_double_boundary(&mut self) {
let uses = std::mem::take(&mut self.long_double_uses);
let mut redirected = std::collections::HashSet::new();
for index in 0..self.program.functions.len() {
let id = FuncId(index as u32);
if !self.is_platform_function(id) {
continue;
}
let func = &mut self.program.functions[index];
if func.asm_label.is_some() {
continue;
}
if let Some(twin) = long_double_twin(&func.name) {
func.asm_label = Some(twin.to_owned());
redirected.insert(id);
}
}
if self.target.platform_long_double_is_double() {
return;
}
let (how, bytes) = match self.target.arch {
Arch::X86_64 => ("on the x87 stack", "sixteen x87 bytes"),
Arch::X86 => ("on the x87 stack", "twelve x87 bytes"),
_ => ("as a 128-bit quad", "sixteen bytes of a 128-bit quad"),
};
for used in uses {
match used {
LongDoubleUse::Function(id, range) => {
if redirected.contains(&id) || !self.is_platform_function(id) {
continue;
}
let name = self.program.function(id).name.clone();
let offence = self.long_double_funcs[&id].offence.unwrap_or_default();
let message = if offence.ends_with("*'") {
format!(
"'{name}' {offence} ('long double' is 'double' here, eight bytes), \
and the platform's function reads or writes {bytes} through it; use \
the 'double' function or wrap it in C compiled by a C compiler"
)
} else {
format!(
"'{name}' {offence} (which is 'double' here), and the platform passes \
a 'long double' {how}, so the call would read the wrong register; \
use the 'double' function or wrap it in C compiled by a C compiler"
)
};
self.error(range, message);
}
LongDoubleUse::Variadic {
func,
range,
pointer,
} => {
if !self.is_platform_function(func) {
continue;
}
let name = self.program.function(func).name.clone();
let message = if pointer {
format!(
"a 'long double *' cannot be passed to the platform's '{name}': \
'long double' is 'double' here, eight bytes, and '{name}' would read \
or write {bytes} through it; pass a 'double *' and use '%lf'"
)
} else {
format!(
"a 'long double' cannot be passed to the platform's '{name}': it is \
'double' here and would be read as {bytes}; cast it to 'double' and \
use '%f'"
)
};
self.error(range, message);
}
}
}
}
}