use rucc_base::float::Format;
use rucc_session::{GnucVersion, OptLevel, Options, Std};
use rucc_target::{Arch, Env, Os, TargetInfo};
pub const BUILT_IN: &str = "<built-in>";
pub const COMMAND_LINE: &str = "<command-line>";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Timestamp {
pub date: String,
pub time: String,
}
impl Timestamp {
pub fn now() -> Timestamp {
let seconds = match std::env::var("SOURCE_DATE_EPOCH").ok().and_then(|v| v.parse().ok()) {
Some(fixed) => fixed,
None => std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs() as i64),
};
Timestamp::from_unix(seconds)
}
pub fn from_unix(seconds: i64) -> Timestamp {
let days = seconds.div_euclid(86_400);
let rest = seconds.rem_euclid(86_400);
let (year, month, day) = civil_from_days(days);
const MONTHS: [&str; 12] =
["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
let name = MONTHS[(month - 1) as usize];
Timestamp {
date: format!("{name} {day:2} {year}"),
time: format!("{:02}:{:02}:{:02}", rest / 3600, (rest / 60) % 60, rest % 60),
}
}
}
fn civil_from_days(days: i64) -> (i64, u32, u32) {
let shifted = days + 719_468;
let era = shifted.div_euclid(146_097);
let day_of_era = shifted.rem_euclid(146_097);
let year_of_era =
(day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
let year = year_of_era + era * 400;
let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
let marched = (5 * day_of_year + 2) / 153;
let day = (day_of_year - (153 * marched + 2) / 5 + 1) as u32;
let month = if marched < 10 { marched + 3 } else { marched - 9 } as u32;
(year + i64::from(month <= 2), month, day)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Predef {
pub std: Std,
pub gnu_extensions: bool,
pub gnuc: GnucVersion,
pub opt_level: OptLevel,
pub hosted: bool,
pub timestamp: Timestamp,
pub defines: Vec<String>,
pub undefines: Vec<String>,
}
impl Predef {
pub fn new() -> Predef {
Predef {
std: Std::default(),
gnu_extensions: true,
gnuc: GnucVersion::default(),
opt_level: OptLevel::O0,
hosted: true,
timestamp: Timestamp::now(),
defines: Vec::new(),
undefines: Vec::new(),
}
}
}
impl Predef {
pub fn for_options(opts: &Options) -> Predef {
Predef {
std: opts.std,
gnu_extensions: opts.gnu_extensions,
gnuc: opts.gnuc,
opt_level: opts.opt_level,
hosted: opts.hosted,
timestamp: Timestamp::now(),
defines: opts.defines.clone(),
undefines: opts.undefines.clone(),
}
}
}
impl Default for Predef {
fn default() -> Predef {
Predef::new()
}
}
struct Defs {
text: String,
}
impl Defs {
fn new() -> Defs {
Defs { text: String::new() }
}
fn set(&mut self, name: &str, value: &str) {
self.text.push_str("#define ");
self.text.push_str(name);
self.text.push(' ');
self.text.push_str(value);
self.text.push('\n');
}
fn flag(&mut self, name: &str) {
self.set(name, "1");
}
fn set_if(&mut self, when: bool, name: &str, value: &str) {
if when {
self.set(name, value);
}
}
fn flag_if(&mut self, when: bool, name: &str) {
if when {
self.flag(name);
}
}
}
pub(crate) fn built_in(target: &TargetInfo, opts: &Predef) -> String {
let mut d = Defs::new();
identity(&mut d, opts);
d.set("__DATE__", &format!("\"{}\"", opts.timestamp.date));
d.set("__TIME__", &format!("\"{}\"", opts.timestamp.time));
dialect(&mut d, opts);
optimization(&mut d, opts);
platform(&mut d, target, opts);
sizes(&mut d, target);
integers(&mut d, target);
floats(&mut d, target);
atomics(&mut d, target);
d.text
}
pub(crate) fn command_line(opts: &Predef) -> String {
let mut d = Defs::new();
for define in &opts.defines {
match define.split_once('=') {
Some((name, value)) => d.set(name, value),
None => d.flag(define),
}
}
for name in &opts.undefines {
d.text.push_str("#undef ");
d.text.push_str(name);
d.text.push('\n');
}
d.text
}
fn identity(d: &mut Defs, opts: &Predef) {
d.flag("__rucc__");
d.set("__rucc_version__", "\"0.1.0\"");
d.set("__rucc_major__", "0");
d.set("__rucc_minor__", "1");
d.set("__rucc_patchlevel__", "0");
d.set("__GNUC__", &opts.gnuc.major.to_string());
d.set("__GNUC_MINOR__", &opts.gnuc.minor.to_string());
d.set("__GNUC_PATCHLEVEL__", &opts.gnuc.patch.to_string());
d.set("__VERSION__", "\"rucc 0.1.0\"");
d.flag("__GNUC_STDC_INLINE__");
}
fn dialect(d: &mut Defs, opts: &Predef) {
d.flag("__STDC__");
d.set_if(opts.hosted, "__STDC_HOSTED__", "1");
d.set_if(!opts.hosted, "__STDC_HOSTED__", "0");
if let Some(version) = opts.std.stdc_version() {
d.set("__STDC_VERSION__", version);
}
d.flag_if(!opts.gnu_extensions, "__STRICT_ANSI__");
d.flag("__STDC_UTF_16__");
d.flag("__STDC_UTF_32__");
d.flag("__STDC_IEC_559__");
d.flag("__STDC_IEC_559_COMPLEX__");
d.set_if(opts.std == Std::C23, "__STDC_IEC_60559_BFP__", "202311L");
d.set("__STDC_ISO_10646__", "201706L");
if opts.std.has_c11() {
d.flag("__STDC_NO_ATOMICS__");
d.flag("__STDC_NO_THREADS__");
d.flag("__STDC_NO_COMPLEX__");
d.flag("__STDC_NO_VLA__");
}
d.set("__STDC_EMBED_NOT_FOUND__", "0");
d.set("__STDC_EMBED_FOUND__", "1");
d.set("__STDC_EMBED_EMPTY__", "2");
}
fn atomics(d: &mut Defs, target: &TargetInfo) {
d.set("__ATOMIC_RELAXED", "0");
d.set("__ATOMIC_CONSUME", "1");
d.set("__ATOMIC_ACQUIRE", "2");
d.set("__ATOMIC_RELEASE", "3");
d.set("__ATOMIC_ACQ_REL", "4");
d.set("__ATOMIC_SEQ_CST", "5");
let llong = if target.pointer_width == 64 { "2" } else { "1" };
for name in [
"BOOL", "CHAR", "CHAR8_T", "CHAR16_T", "CHAR32_T", "WCHAR_T", "SHORT", "INT", "LONG",
"POINTER",
] {
d.set(&format!("__GCC_ATOMIC_{name}_LOCK_FREE"), "2");
}
d.set("__GCC_ATOMIC_LLONG_LOCK_FREE", llong);
d.set("__GCC_ATOMIC_TEST_AND_SET_TRUEVAL", "1");
}
fn optimization(d: &mut Defs, opts: &Predef) {
d.flag_if(opts.opt_level.runs_optimizer(), "__OPTIMIZE__");
d.flag_if(opts.opt_level.is_size(), "__OPTIMIZE_SIZE__");
d.flag_if(!opts.opt_level.runs_optimizer(), "__NO_INLINE__");
}
fn platform(d: &mut Defs, target: &TargetInfo, opts: &Predef) {
let triple = target.triple;
match triple.arch {
Arch::X86_64 => {
d.flag("__x86_64__");
d.flag("__x86_64");
d.flag("__amd64__");
d.flag("__amd64");
d.flag("__SSE__");
d.flag("__SSE2__");
d.flag("__MMX__");
d.flag("__SSE_MATH__");
d.flag("__SSE2_MATH__");
d.flag("__k8");
d.flag("__k8__");
}
Arch::Aarch64 => {
d.flag("__aarch64__");
d.flag("__AARCH64EL__");
d.set("__ARM_ARCH", "8");
d.set("__ARM_ARCH_PROFILE", "'A'");
d.set("__ARM_64BIT_STATE", "1");
d.set("__ARM_ALIGN_MAX_PWR", "28");
d.set("__ARM_FP", "0xe");
d.set("__ARM_NEON", "1");
d.set("__ARM_FEATURE_UNALIGNED", "1");
d.set("__ARM_PCS_AAPCS64", "1");
}
Arch::Riscv64 => {
d.flag("__riscv");
d.set("__riscv_xlen", "64");
d.set("__riscv_flen", "64");
d.flag("__riscv_float_abi_double");
d.flag("__riscv_muldiv");
d.flag("__riscv_atomic");
d.flag("__riscv_compressed");
d.set("__riscv_cmodel_medlow", "1");
}
}
match triple.os {
Os::Linux => {
d.flag("__linux__");
d.flag("__linux");
d.flag("__unix__");
d.flag("__unix");
d.flag("__gnu_linux__");
d.flag("__ELF__");
if opts.gnu_extensions {
d.flag("linux");
d.flag("unix");
}
}
Os::Darwin => {
d.flag("__APPLE__");
d.flag("__MACH__");
d.flag("__unix__");
d.flag("__unix");
d.set("__APPLE_CC__", "6000");
d.set("__DYNAMIC__", "1");
if triple.arch == Arch::Aarch64 {
d.flag("__arm64__");
d.flag("__arm64");
}
if opts.gnu_extensions {
d.flag("unix");
}
}
Os::Windows => {
d.flag("_WIN32");
d.flag("__WIN32__");
d.flag("_WIN64");
d.flag("__WIN64__");
d.flag("__MINGW32__");
}
Os::None => {
d.flag("__ELF__");
}
}
match triple.env {
Env::Musl => d.flag("__musl__"),
Env::Gnu | Env::None | Env::Msvc => {}
}
if target.long_width == 64 && target.pointer_width == 64 {
d.flag("__LP64__");
d.flag("_LP64");
}
d.set("__USER_LABEL_PREFIX__", if triple.os == Os::Darwin { "_" } else { "" });
if !matches!(triple.os, Os::Windows) {
d.set("__PIC__", "2");
d.set("__pic__", "2");
}
}
fn sizes(d: &mut Defs, target: &TargetInfo) {
let pointer = target.pointer_width / 8;
let long = target.long_width / 8;
let long_double = target.long_double_width / 8;
d.set("__CHAR_BIT__", "8");
d.set("__SIZEOF_SHORT__", "2");
d.set("__SIZEOF_INT__", "4");
d.set("__SIZEOF_LONG__", &long.to_string());
d.set("__SIZEOF_LONG_LONG__", "8");
d.set("__SIZEOF_INT128__", "16");
d.set("__SIZEOF_FLOAT__", "4");
d.set("__SIZEOF_DOUBLE__", "8");
d.set("__SIZEOF_LONG_DOUBLE__", &long_double.to_string());
d.set("__SIZEOF_POINTER__", &pointer.to_string());
d.set("__SIZEOF_SIZE_T__", &pointer.to_string());
d.set("__SIZEOF_PTRDIFF_T__", &pointer.to_string());
d.set("__SIZEOF_WCHAR_T__", &wchar(target).size.to_string());
d.set("__SIZEOF_WINT_T__", "4");
d.set("__BIGGEST_ALIGNMENT__", "16");
d.set("__ORDER_LITTLE_ENDIAN__", "1234");
d.set("__ORDER_BIG_ENDIAN__", "4321");
d.set("__ORDER_PDP_ENDIAN__", "3412");
let order =
if target.little_endian { "__ORDER_LITTLE_ENDIAN__" } else { "__ORDER_BIG_ENDIAN__" };
d.set("__BYTE_ORDER__", order);
d.set("__FLOAT_WORD_ORDER__", order);
d.flag_if(!target.char_is_signed, "__CHAR_UNSIGNED__");
}
struct Wchar {
spelling: &'static str,
size: u32,
max: &'static str,
min: &'static str,
}
fn wchar(target: &TargetInfo) -> Wchar {
match (target.wchar_width, target.wchar_is_signed) {
(16, false) => Wchar { spelling: "short unsigned int", size: 2, max: "0xffff", min: "0" },
(16, true) => Wchar { spelling: "short int", size: 2, max: "0x7fff", min: "(-32767 - 1)" },
(_, false) => Wchar { spelling: "unsigned int", size: 4, max: "0xffffffffU", min: "0U" },
(_, true) => {
Wchar { spelling: "int", size: 4, max: "0x7fffffff", min: "(-__WCHAR_MAX__ - 1)" }
}
}
}
struct Wint {
spelling: &'static str,
max: &'static str,
min: &'static str,
}
fn wint(target: &TargetInfo) -> Wint {
match target.triple.os {
Os::Windows => Wint { spelling: "short unsigned int", max: "0xffff", min: "0" },
Os::Darwin => Wint { spelling: "int", max: "2147483647", min: "(-__WINT_MAX__ - 1)" },
_ => Wint { spelling: "unsigned int", max: "4294967295U", min: "0U" },
}
}
fn integers(d: &mut Defs, target: &TargetInfo) {
let lp64 = target.long_width == 64;
let wide = if lp64 { "long int" } else { "long long int" };
let wide_unsigned = if lp64 { "long unsigned int" } else { "long long unsigned int" };
let wide_suffix = if lp64 { "L" } else { "LL" };
let wide_max = format!("9223372036854775807{wide_suffix}");
let wide_umax = format!("18446744073709551615U{wide_suffix}");
d.set("__SCHAR_MAX__", "127");
d.set("__SHRT_MAX__", "32767");
d.set("__INT_MAX__", "2147483647");
d.set("__LONG_MAX__", if lp64 { "9223372036854775807L" } else { "2147483647L" });
d.set("__LONG_LONG_MAX__", "9223372036854775807LL");
d.set("__INTMAX_MAX__", &wide_max);
d.set("__UINTMAX_MAX__", &wide_umax);
d.set("__SIZE_MAX__", &wide_umax);
d.set("__PTRDIFF_MAX__", &wide_max);
d.set("__INTPTR_MAX__", &wide_max);
d.set("__UINTPTR_MAX__", &wide_umax);
d.set("__SIG_ATOMIC_MAX__", "2147483647");
d.set("__SIG_ATOMIC_MIN__", "(-__SIG_ATOMIC_MAX__ - 1)");
d.set("__BITINT_MAXWIDTH__", "128");
let wchar = wchar(target);
d.set("__WCHAR_TYPE__", wchar.spelling);
d.set("__WCHAR_MAX__", wchar.max);
d.set("__WCHAR_MIN__", wchar.min);
let wint = wint(target);
d.set("__WINT_TYPE__", wint.spelling);
d.set("__WINT_MAX__", wint.max);
d.set("__WINT_MIN__", wint.min);
d.set("__SIZE_TYPE__", wide_unsigned);
d.set("__PTRDIFF_TYPE__", wide);
d.set("__INTMAX_TYPE__", wide);
d.set("__UINTMAX_TYPE__", wide_unsigned);
d.set("__INTPTR_TYPE__", wide);
d.set("__UINTPTR_TYPE__", wide_unsigned);
d.set("__SIG_ATOMIC_TYPE__", "int");
d.set("__CHAR16_TYPE__", "short unsigned int");
d.set("__CHAR32_TYPE__", "unsigned int");
d.set("__INTMAX_C(c)", &format!("c ## {wide_suffix}"));
d.set("__UINTMAX_C(c)", &format!("c ## U{wide_suffix}"));
exact(d, 8, "signed char", "unsigned char", "127", "255", "");
exact(d, 16, "short int", "short unsigned int", "32767", "65535", "");
exact(d, 32, "int", "unsigned int", "2147483647", "4294967295U", "");
exact(d, 64, wide, wide_unsigned, &wide_max, &wide_umax, wide_suffix);
let fast_is_wide = target.triple.arch == Arch::X86_64 && lp64 && target.triple.env != Env::Musl;
let fast_middle = if fast_is_wide { wide } else { "int" };
d.set("__INT_FAST8_TYPE__", "signed char");
d.set("__UINT_FAST8_TYPE__", "unsigned char");
d.set("__INT_FAST8_MAX__", "127");
d.set("__UINT_FAST8_MAX__", "255");
for width in [16, 32] {
let unsigned = if fast_middle == "int" { "unsigned int" } else { wide_unsigned };
let max = if fast_middle == "int" { "2147483647" } else { wide_max.as_str() };
let umax = if fast_middle == "int" { "4294967295U" } else { wide_umax.as_str() };
d.set(&format!("__INT_FAST{width}_TYPE__"), fast_middle);
d.set(&format!("__UINT_FAST{width}_TYPE__"), unsigned);
d.set(&format!("__INT_FAST{width}_MAX__"), max);
d.set(&format!("__UINT_FAST{width}_MAX__"), umax);
}
d.set("__INT_FAST64_TYPE__", wide);
d.set("__UINT_FAST64_TYPE__", wide_unsigned);
d.set("__INT_FAST64_MAX__", &wide_max);
d.set("__UINT_FAST64_MAX__", &wide_umax);
}
fn exact(
d: &mut Defs,
width: u32,
signed: &str,
unsigned: &str,
max: &str,
umax: &str,
width_suffix: &str,
) {
d.set(&format!("__INT{width}_TYPE__"), signed);
d.set(&format!("__UINT{width}_TYPE__"), unsigned);
d.set(&format!("__INT{width}_MAX__"), max);
d.set(&format!("__UINT{width}_MAX__"), umax);
d.set(&format!("__INT_LEAST{width}_TYPE__"), signed);
d.set(&format!("__UINT_LEAST{width}_TYPE__"), unsigned);
d.set(&format!("__INT_LEAST{width}_MAX__"), max);
d.set(&format!("__UINT_LEAST{width}_MAX__"), umax);
if width_suffix.is_empty() {
d.set(&format!("__INT{width}_C(c)"), "c");
d.set(&format!("__UINT{width}_C(c)"), "c ## U");
} else {
d.set(&format!("__INT{width}_C(c)"), &format!("c ## {width_suffix}"));
d.set(&format!("__UINT{width}_C(c)"), &format!("c ## U{width_suffix}"));
}
}
struct Characteristics {
mant_dig: &'static str,
dig: &'static str,
min_exp: &'static str,
min_10_exp: &'static str,
max_exp: &'static str,
max_10_exp: &'static str,
decimal_dig: &'static str,
max: &'static str,
min: &'static str,
epsilon: &'static str,
denorm_min: &'static str,
is_iec_60559: &'static str,
}
const HALF: Characteristics = Characteristics {
mant_dig: "11",
dig: "3",
min_exp: "(-13)",
min_10_exp: "(-4)",
max_exp: "16",
max_10_exp: "4",
decimal_dig: "5",
max: "6.55040000000000000000000000000000000e+4",
min: "6.10351562500000000000000000000000000e-5",
epsilon: "9.76562500000000000000000000000000000e-4",
denorm_min: "5.96046447753906250000000000000000000e-8",
is_iec_60559: "1",
};
const BFLOAT16: Characteristics = Characteristics {
mant_dig: "8",
dig: "2",
min_exp: "(-125)",
min_10_exp: "(-37)",
max_exp: "128",
max_10_exp: "38",
decimal_dig: "4",
max: "3.38953138925153547590470800371487867e+38",
min: "1.17549435082228750796873653722224568e-38",
epsilon: "7.81250000000000000000000000000000000e-3",
denorm_min: "9.18354961579912115600575419704879436e-41",
is_iec_60559: "0",
};
const SINGLE: Characteristics = Characteristics {
mant_dig: "24",
dig: "6",
min_exp: "(-125)",
min_10_exp: "(-37)",
max_exp: "128",
max_10_exp: "38",
decimal_dig: "9",
max: "3.40282346638528859811704183484516925e+38",
min: "1.17549435082228750796873653722224568e-38",
epsilon: "1.19209289550781250000000000000000000e-7",
denorm_min: "1.40129846432481707092372958328991613e-45",
is_iec_60559: "1",
};
const DOUBLE: Characteristics = Characteristics {
mant_dig: "53",
dig: "15",
min_exp: "(-1021)",
min_10_exp: "(-307)",
max_exp: "1024",
max_10_exp: "308",
decimal_dig: "17",
max: "1.79769313486231570814527423731704357e+308",
min: "2.22507385850720138309023271733240406e-308",
epsilon: "2.22044604925031308084726333618164062e-16",
denorm_min: "4.94065645841246544176568792868221372e-324",
is_iec_60559: "1",
};
const X87: Characteristics = Characteristics {
mant_dig: "64",
dig: "18",
min_exp: "(-16381)",
min_10_exp: "(-4931)",
max_exp: "16384",
max_10_exp: "4932",
decimal_dig: "21",
max: "1.18973149535723176502126385303097021e+4932",
min: "3.36210314311209350626267781732175260e-4932",
epsilon: "1.08420217248550443400745280086994171e-19",
denorm_min: "3.64519953188247460252840593361941982e-4951",
is_iec_60559: "1",
};
const QUAD: Characteristics = Characteristics {
mant_dig: "113",
dig: "33",
min_exp: "(-16381)",
min_10_exp: "(-4931)",
max_exp: "16384",
max_10_exp: "4932",
decimal_dig: "36",
max: "1.18973149535723176508575932662800702e+4932",
min: "3.36210314311209350626267781732175260e-4932",
epsilon: "1.92592994438723585305597794258492732e-34",
denorm_min: "6.47517511943802511092443895822764655e-4966",
is_iec_60559: "1",
};
const fn characteristics(format: Format) -> &'static Characteristics {
match format {
Format::Half => &HALF,
Format::BFloat16 => &BFLOAT16,
Format::Single => &SINGLE,
Format::Double => &DOUBLE,
Format::X87Extended => &X87,
Format::Quad => &QUAD,
}
}
fn floats(d: &mut Defs, target: &TargetInfo) {
d.set("__FLT_RADIX__", "2");
d.set("__FLT_EVAL_METHOD__", "0");
d.set("__FLT_EVAL_METHOD_C99__", "0");
d.set("__FLT_EVAL_METHOD_TS_18661_3__", "0");
family(d, "FLT", &SINGLE, |value| format!("{value}F"));
family(d, "DBL", &DOUBLE, |value| format!("((double){value}L)"));
family(d, "LDBL", characteristics(target.long_double_format), |value| format!("{value}L"));
family(d, "FLT16", &HALF, |value| format!("{value}F16"));
family(d, "FLT32", &SINGLE, |value| format!("{value}F32"));
family(d, "FLT64", &DOUBLE, |value| format!("{value}F64"));
family(d, "FLT128", &QUAD, |value| format!("{value}F128"));
family(d, "FLT32X", &DOUBLE, |value| format!("{value}F32x"));
family(d, "FLT64X", characteristics(target.float64x_format), |value| format!("{value}F64x"));
d.set("__DECIMAL_DIG__", "__LDBL_DECIMAL_DIG__");
}
fn family(d: &mut Defs, prefix: &str, c: &Characteristics, write: impl Fn(&str) -> String) {
d.set(&format!("__{prefix}_MANT_DIG__"), c.mant_dig);
d.set(&format!("__{prefix}_DIG__"), c.dig);
d.set(&format!("__{prefix}_MIN_EXP__"), c.min_exp);
d.set(&format!("__{prefix}_MIN_10_EXP__"), c.min_10_exp);
d.set(&format!("__{prefix}_MAX_EXP__"), c.max_exp);
d.set(&format!("__{prefix}_MAX_10_EXP__"), c.max_10_exp);
d.set(&format!("__{prefix}_DECIMAL_DIG__"), c.decimal_dig);
d.set(&format!("__{prefix}_MAX__"), &write(c.max));
d.set(&format!("__{prefix}_NORM_MAX__"), &write(c.max));
d.set(&format!("__{prefix}_MIN__"), &write(c.min));
d.set(&format!("__{prefix}_EPSILON__"), &write(c.epsilon));
d.set(&format!("__{prefix}_DENORM_MIN__"), &write(c.denorm_min));
d.set(&format!("__{prefix}_IS_IEC_60559__"), c.is_iec_60559);
d.set(&format!("__{prefix}_HAS_DENORM__"), "1");
d.set(&format!("__{prefix}_HAS_INFINITY__"), "1");
d.set(&format!("__{prefix}_HAS_QUIET_NAN__"), "1");
}
#[cfg(test)]
mod tests {
use rucc_target::Triple;
use super::*;
fn set_for(triple: &str) -> String {
let triple: Triple = triple.parse().expect("a triple the compiler supports");
built_in(&TargetInfo::new(triple), &Predef::new())
}
fn has(text: &str, line: &str) -> bool {
text.lines().any(|l| l == line)
}
#[test]
fn the_set_is_driven_by_the_target_rather_than_by_the_host() {
let x86 = set_for("x86_64-unknown-linux-gnu");
let arm = set_for("aarch64-unknown-linux-gnu");
assert!(has(&x86, "#define __x86_64__ 1"));
assert!(!has(&x86, "#define __aarch64__ 1"));
assert!(has(&arm, "#define __aarch64__ 1"));
assert!(!has(&arm, "#define __x86_64__ 1"));
assert!(has(&x86, "#define __linux__ 1") && has(&arm, "#define __linux__ 1"));
}
#[test]
fn windows_is_the_target_that_makes_long_thirty_two_bits() {
let windows = set_for("x86_64-pc-windows-msvc");
let linux = set_for("x86_64-unknown-linux-gnu");
assert!(has(&windows, "#define __SIZEOF_LONG__ 4"));
assert!(has(&windows, "#define __SIZE_TYPE__ long long unsigned int"));
assert!(has(&windows, "#define __INT64_TYPE__ long long int"));
assert!(!has(&windows, "#define __LP64__ 1"));
assert!(has(&linux, "#define __SIZEOF_LONG__ 8"));
assert!(has(&linux, "#define __SIZE_TYPE__ long unsigned int"));
assert!(has(&linux, "#define __INT64_TYPE__ long int"));
assert!(has(&linux, "#define __LP64__ 1"));
}
#[test]
fn wchar_t_is_the_type_that_divides_the_targets() {
assert!(has(&set_for("x86_64-unknown-linux-gnu"), "#define __WCHAR_TYPE__ int"));
assert!(has(&set_for("aarch64-unknown-linux-gnu"), "#define __WCHAR_TYPE__ unsigned int"));
let windows = set_for("x86_64-pc-windows-msvc");
assert!(has(&windows, "#define __WCHAR_TYPE__ short unsigned int"));
assert!(has(&windows, "#define __SIZEOF_WCHAR_T__ 2"));
}
#[test]
fn apple_spells_the_architecture_its_own_way_and_its_headers_only_know_that_spelling() {
let darwin = set_for("aarch64-apple-darwin");
assert!(has(&darwin, "#define __arm64__ 1"));
assert!(has(&darwin, "#define __arm64 1"));
assert!(has(&darwin, "#define __aarch64__ 1"), "the portable spelling stays too");
let linux = set_for("aarch64-unknown-linux-gnu");
assert!(!has(&linux, "#define __arm64__ 1"), "Apple's spelling is Apple's alone");
assert!(!has(&set_for("x86_64-apple-darwin"), "#define __arm64__ 1"));
}
#[test]
fn wint_t_does_not_follow_wchar_t() {
let darwin = set_for("aarch64-apple-darwin");
assert!(has(&darwin, "#define __WINT_TYPE__ int"));
assert!(has(&darwin, "#define __WINT_MAX__ 2147483647"));
assert!(has(&darwin, "#define __WCHAR_TYPE__ int"));
let linux = set_for("aarch64-unknown-linux-gnu");
assert!(has(&linux, "#define __WINT_TYPE__ unsigned int"));
assert!(has(&linux, "#define __WINT_MAX__ 4294967295U"));
assert!(has(&linux, "#define __WCHAR_TYPE__ unsigned int"), "and wchar_t is its own");
assert!(has(
&set_for("x86_64-pc-windows-msvc"),
"#define __WINT_TYPE__ short unsigned int"
));
}
#[test]
fn a_constant_maker_gets_the_suffix_its_width_needs_and_no_other() {
let linux = set_for("x86_64-unknown-linux-gnu");
assert!(has(&linux, "#define __INT32_C(c) c"));
assert!(has(&linux, "#define __UINT32_C(c) c ## U"));
assert!(has(&linux, "#define __INT16_C(c) c"));
assert!(has(&linux, "#define __UINT16_C(c) c ## U"));
assert!(has(&linux, "#define __INT64_C(c) c ## L"));
assert!(has(&linux, "#define __UINT64_C(c) c ## UL"));
let windows = set_for("x86_64-pc-windows-msvc");
assert!(has(&windows, "#define __INT64_C(c) c ## LL"));
assert!(has(&windows, "#define __UINT64_C(c) c ## ULL"));
}
#[test]
fn the_symbol_prefix_is_defined_everywhere_including_where_it_is_empty() {
for triple in
["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
{
assert!(has(&set_for(triple), "#define __USER_LABEL_PREFIX__ "), "{triple}");
}
assert!(has(&set_for("aarch64-apple-darwin"), "#define __USER_LABEL_PREFIX__ _"));
}
#[test]
fn the_memory_orders_are_there_even_without_atomics() {
let linux = set_for("x86_64-unknown-linux-gnu");
assert!(has(&linux, "#define __ATOMIC_RELAXED 0"));
assert!(has(&linux, "#define __ATOMIC_SEQ_CST 5"));
assert!(has(&linux, "#define __STDC_NO_ATOMICS__ 1"), "and we still have no _Atomic");
assert!(has(&linux, "#define __GCC_ATOMIC_INT_LOCK_FREE 2"));
assert!(has(&linux, "#define __GCC_ATOMIC_LLONG_LOCK_FREE 2"));
assert!(has(&set_for("x86_64-pc-windows-msvc"), "#define __GCC_ATOMIC_LLONG_LOCK_FREE 2"));
}
#[test]
fn long_double_is_three_types_and_the_macros_say_which() {
assert!(has(&set_for("x86_64-unknown-linux-gnu"), "#define __LDBL_MANT_DIG__ 64"));
assert!(has(&set_for("aarch64-unknown-linux-gnu"), "#define __LDBL_MANT_DIG__ 113"));
assert!(has(&set_for("aarch64-apple-darwin"), "#define __LDBL_MANT_DIG__ 53"));
}
#[test]
fn the_extended_floating_types_have_the_limits_their_formats_have() {
let linux = set_for("x86_64-unknown-linux-gnu");
assert!(has(&linux, "#define __FLT16_MANT_DIG__ 11"));
assert!(has(&linux, "#define __FLT32_MANT_DIG__ 24"));
assert!(has(&linux, "#define __FLT64_MANT_DIG__ 53"));
assert!(has(&linux, "#define __FLT128_MANT_DIG__ 113"));
assert!(has(&linux, "#define __FLT32X_MANT_DIG__ 53"));
assert!(has(&linux, "#define __FLT16_MAX__ 6.55040000000000000000000000000000000e+4F16"));
assert!(has(
&linux,
"#define __FLT32X_MIN__ 2.22507385850720138309023271733240406e-308F32x"
));
assert!(!linux.contains("__FLT128X_"));
}
#[test]
fn float64x_keeps_the_width_that_long_double_loses_on_apple() {
let linux = set_for("x86_64-unknown-linux-gnu");
assert!(has(&linux, "#define __FLT64X_MANT_DIG__ 64"));
assert!(has(&linux, "#define __LDBL_MANT_DIG__ 64"));
let mac = set_for("aarch64-apple-darwin");
assert!(has(&mac, "#define __FLT64X_MANT_DIG__ 113"));
assert!(has(&mac, "#define __LDBL_MANT_DIG__ 53"));
let windows = set_for("x86_64-pc-windows-msvc");
assert!(has(&windows, "#define __FLT64X_MANT_DIG__ 64"));
assert!(has(&windows, "#define __LDBL_MANT_DIG__ 53"));
}
#[test]
fn the_largest_value_of_a_binary_format_is_also_its_largest_normal_one() {
let linux = set_for("x86_64-unknown-linux-gnu");
for prefix in ["FLT", "DBL", "LDBL", "FLT16", "FLT32", "FLT64", "FLT128", "FLT32X"] {
let value = |suffix: &str| {
let name = format!("#define __{prefix}_{suffix}__ ");
let line = linux
.lines()
.find(|line| line.starts_with(&name))
.unwrap_or_else(|| panic!("__{prefix}_{suffix}__ is defined"));
line[name.len()..].to_owned()
};
assert_eq!(value("MAX"), value("NORM_MAX"), "__{prefix}_NORM_MAX__");
}
}
#[test]
fn the_widest_bit_int_is_said_in_every_dialect() {
let mut opts = Predef::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
assert!(has(&built_in(&target, &opts), "#define __BITINT_MAXWIDTH__ 128"));
opts.std = Std::C17;
assert!(has(&built_in(&target, &opts), "#define __BITINT_MAXWIDTH__ 128"));
}
#[test]
fn char_signedness_is_recorded_only_when_it_is_unsigned() {
assert!(has(&set_for("aarch64-unknown-linux-gnu"), "#define __CHAR_UNSIGNED__ 1"));
assert!(!has(&set_for("x86_64-unknown-linux-gnu"), "#define __CHAR_UNSIGNED__ 1"));
}
#[test]
fn the_dialect_decides_the_standard_macros() {
let mut opts = Predef::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
assert!(has(&built_in(&target, &opts), "#define __STDC_VERSION__ 202311L"));
assert!(!has(&built_in(&target, &opts), "#define __STRICT_ANSI__ 1"));
assert!(has(&built_in(&target, &opts), "#define linux 1"));
opts.gnu_extensions = false;
assert!(has(&built_in(&target, &opts), "#define __STRICT_ANSI__ 1"));
assert!(!has(&built_in(&target, &opts), "#define linux 1"), "not a reserved name");
opts.std = Std::C89;
let c89 = built_in(&target, &opts);
assert!(!c89.contains("__STDC_VERSION__"), "C89 does not define it at all");
assert!(has(&c89, "#define __STDC__ 1"));
}
#[test]
fn the_optimizer_level_is_visible_to_the_preprocessor() {
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
let mut opts = Predef::new();
assert!(has(&built_in(&target, &opts), "#define __NO_INLINE__ 1"));
assert!(!built_in(&target, &opts).contains("__OPTIMIZE__"));
opts.opt_level = OptLevel::O2;
assert!(has(&built_in(&target, &opts), "#define __OPTIMIZE__ 1"));
assert!(!built_in(&target, &opts).contains("__OPTIMIZE_SIZE__"));
opts.opt_level = OptLevel::Os;
assert!(has(&built_in(&target, &opts), "#define __OPTIMIZE_SIZE__ 1"));
}
#[test]
fn a_command_line_define_with_no_value_is_one() {
let mut opts = Predef::new();
opts.defines = vec!["FOO".to_owned(), "BAR=2".to_owned(), "F(x)=x + 1".to_owned()];
opts.undefines = vec!["__linux__".to_owned()];
let text = command_line(&opts);
assert!(has(&text, "#define FOO 1"));
assert!(has(&text, "#define BAR 2"));
assert!(has(&text, "#define F(x) x + 1"));
assert!(text.trim_end().ends_with("#undef __linux__"));
}
#[test]
fn no_command_line_macros_is_no_file_at_all() {
assert!(command_line(&Predef::new()).is_empty());
}
#[test]
fn a_date_is_spelled_the_way_the_standard_fixes() {
let epoch = Timestamp::from_unix(0);
assert_eq!(epoch.date, "Jan 1 1970");
assert_eq!(epoch.time, "00:00:00");
let leap = Timestamp::from_unix(1_709_164_800);
assert_eq!(leap.date, "Feb 29 2024", "2024 is a leap year");
let late = Timestamp::from_unix(1_735_689_599);
assert_eq!(late.date, "Dec 31 2024");
assert_eq!(late.time, "23:59:59");
}
#[test]
fn a_date_before_the_epoch_still_comes_out_right() {
assert_eq!(Timestamp::from_unix(-1).date, "Dec 31 1969");
assert_eq!(Timestamp::from_unix(-1).time, "23:59:59");
}
#[test]
fn the_gnuc_version_is_a_knob_rather_than_a_constant() {
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
let mut opts = Predef::new();
assert!(has(&built_in(&target, &opts), "#define __GNUC__ 4"));
opts.gnuc = GnucVersion { major: 15, minor: 1, patch: 0 };
assert!(has(&built_in(&target, &opts), "#define __GNUC__ 15"));
assert!(has(&built_in(&target, &opts), "#define __GNUC_MINOR__ 1"));
}
#[test]
fn musl_and_glibc_disagree_about_the_fast_types_on_the_same_processor() {
let gnu = set_for("x86_64-unknown-linux-gnu");
let musl = set_for("x86_64-unknown-linux-musl");
assert!(has(&gnu, "#define __INT_FAST16_TYPE__ long int"));
assert!(has(&gnu, "#define __INT_FAST32_TYPE__ long int"));
assert!(has(&gnu, "#define __UINT_FAST16_TYPE__ long unsigned int"));
assert!(has(&musl, "#define __INT_FAST16_TYPE__ int"));
assert!(has(&musl, "#define __INT_FAST32_TYPE__ int"));
assert!(has(&musl, "#define __UINT_FAST16_TYPE__ unsigned int"));
assert!(has(&gnu, "#define __INT_FAST16_MAX__ 9223372036854775807L"));
assert!(has(&musl, "#define __INT_FAST16_MAX__ 2147483647"));
assert!(has(&musl, "#define __UINT_FAST16_MAX__ 4294967295U"));
}
#[test]
fn the_libc_only_moves_the_two_fast_types_it_is_allowed_to_move() {
let gnu = set_for("x86_64-unknown-linux-gnu");
let musl = set_for("x86_64-unknown-linux-musl");
for line in [
"#define __INT_FAST8_TYPE__ signed char",
"#define __INT_FAST64_TYPE__ long int",
"#define __INT64_TYPE__ long int",
"#define __SIZE_TYPE__ long unsigned int",
"#define __SIZEOF_LONG__ 8",
"#define __LP64__ 1",
] {
assert!(has(&gnu, line), "glibc lost {line}");
assert!(has(&musl, line), "musl lost {line}");
}
}
#[test]
fn a_non_x86_target_has_int_sized_fast_types_whatever_the_libc() {
let arm_gnu = set_for("aarch64-unknown-linux-gnu");
let arm_musl = set_for("aarch64-unknown-linux-musl");
assert!(has(&arm_gnu, "#define __INT_FAST16_TYPE__ int"));
assert!(has(&arm_musl, "#define __INT_FAST16_TYPE__ int"));
}
}