use std::fmt::Write as _;
use std::path::Path;
use std::process::ExitCode;
use crate::rng::Rng;
const DIR: &str = "tests/abi-signatures";
const SEED: u64 = 0x5243_4300_4162_6944;
const DRAWN: usize = 48;
const ANCHOR: u64 = 64;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Mode {
Write,
Check,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Scalar {
Char,
SChar,
UChar,
Short,
UShort,
Int,
UInt,
Long,
ULong,
LongLong,
ULongLong,
Float,
Double,
LongDouble,
Pointer,
}
impl Scalar {
fn spelling(self) -> &'static str {
match self {
Scalar::Char => "char",
Scalar::SChar => "signed char",
Scalar::UChar => "unsigned char",
Scalar::Short => "short",
Scalar::UShort => "unsigned short",
Scalar::Int => "int",
Scalar::UInt => "unsigned int",
Scalar::Long => "long",
Scalar::ULong => "unsigned long",
Scalar::LongLong => "long long",
Scalar::ULongLong => "unsigned long long",
Scalar::Float => "float",
Scalar::Double => "double",
Scalar::LongDouble => "long double",
Scalar::Pointer => "void *",
}
}
}
const SCALARS: &[Scalar] = &[
Scalar::Char,
Scalar::SChar,
Scalar::UChar,
Scalar::Short,
Scalar::UShort,
Scalar::Int,
Scalar::UInt,
Scalar::Long,
Scalar::ULong,
Scalar::LongLong,
Scalar::ULongLong,
Scalar::Float,
Scalar::Double,
Scalar::LongDouble,
Scalar::Pointer,
];
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Struct,
Union,
}
enum Member {
Scalar(&'static str, Scalar),
Nested(&'static str, usize),
}
struct Aggregate {
name: &'static str,
kind: Kind,
why: &'static str,
members: &'static [Member],
}
const AGGREGATES: &[Aggregate] = &[
Aggregate {
name: "one_char",
kind: Kind::Struct,
why: "One byte in a struct, which is a register on every ABI here and a different \
register from the one a bare char would use on none of them.",
members: &[Member::Scalar("a", Scalar::Char)],
},
Aggregate {
name: "three_char",
kind: Kind::Struct,
why: "Three bytes, so the size is not a power of two and the last byte of the register \
it travels in is nobody's.",
members: &[
Member::Scalar("a", Scalar::Char),
Member::Scalar("b", Scalar::Char),
Member::Scalar("c", Scalar::Char),
],
},
Aggregate {
name: "two_int",
kind: Kind::Struct,
why: "Eight bytes of integer, which is one register everywhere and the smallest \
aggregate that fills one.",
members: &[Member::Scalar("a", Scalar::Int), Member::Scalar("b", Scalar::Int)],
},
Aggregate {
name: "int_float",
kind: Kind::Struct,
why: "An int and a float in one eightbyte. SysV classifies the eightbyte by what is in \
it, so this is an integer register and two floats in the same space are not.",
members: &[Member::Scalar("a", Scalar::Int), Member::Scalar("b", Scalar::Float)],
},
Aggregate {
name: "two_float",
kind: Kind::Struct,
why: "Two floats, which is a homogeneous aggregate in two vector registers under AAPCS64 \
and one SSE register holding both under SysV.",
members: &[Member::Scalar("a", Scalar::Float), Member::Scalar("b", Scalar::Float)],
},
Aggregate {
name: "four_float",
kind: Kind::Struct,
why: "Four floats, which is the largest homogeneous aggregate AAPCS64 will put in \
registers and sixteen bytes of SSE under SysV.",
members: &[
Member::Scalar("a", Scalar::Float),
Member::Scalar("b", Scalar::Float),
Member::Scalar("c", Scalar::Float),
Member::Scalar("d", Scalar::Float),
],
},
Aggregate {
name: "two_double",
kind: Kind::Struct,
why: "Sixteen bytes of floating point, which is two registers under both rules and a \
hidden pointer under Windows x64.",
members: &[Member::Scalar("a", Scalar::Double), Member::Scalar("b", Scalar::Double)],
},
Aggregate {
name: "long_double_one",
kind: Kind::Struct,
why: "A long double in a struct, which is the x87 eighty bit type on two rows, an IEEE \
quad on several, a double double on ppc64le and a plain double on MSVC.",
members: &[Member::Scalar("a", Scalar::LongDouble)],
},
Aggregate {
name: "int_pointer",
kind: Kind::Struct,
why: "An int and a pointer, so the size and the padding both move with the pointer \
width and the member after the padding is what says whether they moved together.",
members: &[Member::Scalar("a", Scalar::Int), Member::Scalar("b", Scalar::Pointer)],
},
Aggregate {
name: "six_int",
kind: Kind::Struct,
why: "Twenty four bytes, which is past the threshold on every ABI here, so it travels \
as a copy the callee is given the address of rather than in registers.",
members: &[
Member::Scalar("a", Scalar::Int),
Member::Scalar("b", Scalar::Int),
Member::Scalar("c", Scalar::Int),
Member::Scalar("d", Scalar::Int),
Member::Scalar("e", Scalar::Int),
Member::Scalar("f", Scalar::Int),
],
},
Aggregate {
name: "nested",
kind: Kind::Struct,
why: "A struct inside a struct with a float after it. Flattening is what every \
classification rule does first, so a rule that stops at the outer members gets \
this one wrong and gets nothing else wrong.",
members: &[Member::Nested("a", 2), Member::Scalar("b", Scalar::Float)],
},
Aggregate {
name: "int_or_float",
kind: Kind::Union,
why: "A union of an int and a float, which is one eightbyte with two classifications \
and is why SysV's rule is a merge rather than a lookup.",
members: &[Member::Scalar("a", Scalar::Int), Member::Scalar("b", Scalar::Float)],
},
];
#[derive(Clone, Copy, PartialEq, Eq)]
enum Ty {
Scalar(Scalar),
Aggregate(usize),
}
impl Ty {
fn spelling(self) -> String {
match self {
Ty::Scalar(scalar) => scalar.spelling().to_string(),
Ty::Aggregate(at) => {
let aggregate = &AGGREGATES[at];
let keyword = match aggregate.kind {
Kind::Struct => "struct",
Kind::Union => "union",
};
format!("{keyword} {}", aggregate.name)
}
}
}
fn leaves(self) -> Vec<(String, Scalar)> {
match self {
Ty::Scalar(scalar) => vec![(String::new(), scalar)],
Ty::Aggregate(at) => {
let aggregate = &AGGREGATES[at];
let members: &[Member] = match aggregate.kind {
Kind::Struct => aggregate.members,
Kind::Union => &aggregate.members[..1],
};
let mut leaves = Vec::new();
for member in members {
match member {
Member::Scalar(name, scalar) => {
leaves.push((format!(".{name}"), *scalar));
}
Member::Nested(name, inner) => {
for (path, scalar) in Ty::Aggregate(*inner).leaves() {
leaves.push((format!(".{name}{path}"), scalar));
}
}
}
}
leaves
}
}
}
fn initializer(self, values: &[String], next: &mut usize) -> String {
match self {
Ty::Scalar(_) => {
let value = values[*next].clone();
*next += 1;
value
}
Ty::Aggregate(at) => {
let aggregate = &AGGREGATES[at];
let members: &[Member] = match aggregate.kind {
Kind::Struct => aggregate.members,
Kind::Union => &aggregate.members[..1],
};
let mut parts = Vec::new();
for member in members {
let part = match member {
Member::Scalar(_, scalar) => Ty::Scalar(*scalar).initializer(values, next),
Member::Nested(_, inner) => Ty::Aggregate(*inner).initializer(values, next),
};
parts.push(part);
}
format!("{{ {} }}", parts.join(", "))
}
}
}
}
struct Signature {
name: String,
why: &'static str,
ret: Option<Ty>,
ret_values: Vec<String>,
params: Vec<Param>,
}
struct Param {
name: String,
ty: Ty,
values: Vec<String>,
}
struct Values(u64);
impl Values {
fn new() -> Values {
Values(0)
}
fn next(&mut self, scalar: Scalar) -> String {
self.0 += 1;
let n = self.0;
match scalar {
Scalar::Char | Scalar::SChar => format!("({})({})", scalar.spelling(), 1 + n % 100),
Scalar::UChar => format!("(unsigned char)({})", 1 + n % 240),
Scalar::Short => format!("(short)({})", 1 + n % 30000),
Scalar::UShort => format!("(unsigned short)({})", 1 + n % 60000),
Scalar::Int => format!("{}", 1 + n * 7919 % 2_000_000_000),
Scalar::UInt => format!("{}u", 1 + n * 7919 % 4_000_000_000),
Scalar::Long => format!("{}L", 1 + n * 7919 % 2_000_000_000),
Scalar::ULong => format!("{}UL", 1 + n * 7919 % 4_000_000_000),
Scalar::LongLong => {
format!("{}LL", 1 + n.wrapping_mul(0x0001_0f2c_3d4e_5f60) % 9_000_000_000_000_000)
}
Scalar::ULongLong => {
format!("{}ULL", 1 + n.wrapping_mul(0x0001_0f2c_3d4e_5f60) % 18_000_000_000_000_000)
}
Scalar::Float => format!("{}.25f", 1 + n % 1000),
Scalar::Double => format!("{}.25", 1 + n % 100_000),
Scalar::LongDouble => format!("{}.25L", 1 + n % 100_000),
Scalar::Pointer => format!("(void *)&anchor[{}]", n % ANCHOR),
}
}
fn for_type(&mut self, ty: Ty) -> Vec<String> {
ty.leaves().into_iter().map(|(_, scalar)| self.next(scalar)).collect()
}
}
pub(crate) fn run(root: &Path, mode: Mode) -> ExitCode {
let signatures = signatures();
let files = [
("abi.h", header(&signatures)),
("report.c", report()),
("callee.c", callee(&signatures)),
("caller.c", caller(&signatures)),
];
let dir = root.join(DIR);
if mode == Mode::Write {
if let Err(error) = std::fs::create_dir_all(&dir) {
eprintln!("error: could not create {}: {error}", dir.display());
return ExitCode::FAILURE;
}
}
let mut stale = Vec::new();
for (name, wanted) in &files {
let path = dir.join(name);
let found = std::fs::read_to_string(&path).ok();
if found.as_deref() == Some(wanted.as_str()) {
continue;
}
if mode == Mode::Check {
stale.push(*name);
continue;
}
if let Err(error) = std::fs::write(&path, wanted) {
eprintln!("error: could not write {}: {error}", path.display());
return ExitCode::FAILURE;
}
}
if mode == Mode::Check {
if stale.is_empty() {
println!(
"abi-signatures: {} functions in {} files are up to date",
signatures.len(),
files.len()
);
return ExitCode::SUCCESS;
}
println!(
"abi-signatures: {} files are out of date, run `cargo xtask abi-signatures`",
stale.len()
);
for name in stale {
println!(" {DIR}/{name}");
}
return ExitCode::FAILURE;
}
println!("abi-signatures: wrote {} functions to {DIR}", signatures.len());
ExitCode::SUCCESS
}
fn signatures() -> Vec<Signature> {
let mut values = Values::new();
let mut out = Vec::new();
let mut named = |name: &str, why: &'static str, ret: Option<Ty>, params: Vec<Ty>| {
out.push(build(&mut values, name.to_string(), why, ret, params));
};
named(
"h_ints_past_the_registers",
"Ten integers, which is more than any ABI here has argument registers, so the tail is on \
the stack and the boundary between the two is what this is about.",
Some(Ty::Scalar(Scalar::LongLong)),
vec![Ty::Scalar(Scalar::LongLong); 10],
);
named(
"h_doubles_past_the_registers",
"Ten doubles, for the same reason and the other register file. SysV has eight of these \
and Windows x64 has four, so the two disagree about where the fifth one is.",
Some(Ty::Scalar(Scalar::Double)),
vec![Ty::Scalar(Scalar::Double); 10],
);
named(
"h_mixed_past_the_registers",
"Integers and doubles alternating past the end of both files, which is where an ABI that \
counts one register file decides differently from one that counts a slot per argument.",
Some(Ty::Scalar(Scalar::Int)),
vec![
Ty::Scalar(Scalar::Int),
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::Int),
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::Int),
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::Int),
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::Int),
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::Int),
Ty::Scalar(Scalar::Double),
],
);
named(
"h_small_aggregates",
"The aggregates that fit in registers, in one call, so the classification of each one is \
checked with the register file already partly spent.",
Some(Ty::Aggregate(2)),
vec![Ty::Aggregate(0), Ty::Aggregate(1), Ty::Aggregate(2), Ty::Aggregate(3)],
);
named(
"h_float_aggregates",
"The homogeneous floating point aggregates, which are the ones AAPCS64 puts in vector \
registers and SysV packs into SSE eightbytes.",
Some(Ty::Aggregate(5)),
vec![Ty::Aggregate(4), Ty::Aggregate(5), Ty::Aggregate(6)],
);
named(
"h_memory_aggregate",
"The aggregate that is too large for registers, with an integer either side of it, so a \
caller that forgets it left a copy behind puts the next argument in the wrong place.",
Some(Ty::Aggregate(9)),
vec![Ty::Scalar(Scalar::Int), Ty::Aggregate(9), Ty::Scalar(Scalar::Int)],
);
named(
"h_returns_by_hidden_pointer",
"A large aggregate returned, which every ABI here does by giving the callee the address \
to write it to. That address is an argument nobody wrote, so it moves every other one.",
Some(Ty::Aggregate(9)),
vec![Ty::Scalar(Scalar::Int), Ty::Scalar(Scalar::Double)],
);
named(
"h_returns_nothing",
"A function returning void with arguments that fill the registers, because the return \
value is what an ABI spends a register on before the arguments and this is the case \
where it does not.",
None,
vec![
Ty::Aggregate(2),
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::LongDouble),
Ty::Scalar(Scalar::Pointer),
],
);
named(
"h_long_double_and_friends",
"A long double between two integers, which is the type whose size, alignment and format \
all move between rows of the table.",
Some(Ty::Scalar(Scalar::LongDouble)),
vec![Ty::Scalar(Scalar::Int), Ty::Scalar(Scalar::LongDouble), Ty::Scalar(Scalar::Int)],
);
named(
"h_nested_and_union",
"The nested aggregate and the union, which are the two shapes a classification rule has \
to flatten before it can decide anything.",
Some(Ty::Aggregate(11)),
vec![Ty::Aggregate(10), Ty::Aggregate(11), Ty::Aggregate(8)],
);
let mut rng = Rng::new(SEED);
for index in 0..DRAWN {
let count = rng.below(9) as usize;
let params: Vec<Ty> = (0..count).map(|_| draw(&mut rng)).collect();
let ret = if rng.below(8) == 0 { None } else { Some(draw(&mut rng)) };
out.push(build(&mut values, format!("g{index:02}"), "", ret, params));
}
out
}
fn draw(rng: &mut Rng) -> Ty {
if rng.below(3) == 0 {
Ty::Aggregate(rng.below(AGGREGATES.len() as u64) as usize)
} else {
Ty::Scalar(SCALARS[rng.below(SCALARS.len() as u64) as usize])
}
}
fn build(
values: &mut Values,
name: String,
why: &'static str,
ret: Option<Ty>,
params: Vec<Ty>,
) -> Signature {
let params = params
.into_iter()
.enumerate()
.map(|(index, ty)| Param { name: format!("a{index}"), values: values.for_type(ty), ty })
.collect();
let ret_values = match ret {
Some(ty) => values.for_type(ty),
None => Vec::new(),
};
Signature { name, why, ret, ret_values, params }
}
fn prototype(signature: &Signature) -> String {
let params = if signature.params.is_empty() {
"void".to_string()
} else {
signature
.params
.iter()
.map(|param| declarator(param.ty, ¶m.name))
.collect::<Vec<_>>()
.join(", ")
};
match signature.ret {
Some(ty) => declarator(ty, &format!("{}({params})", signature.name)),
None => format!("void {}({params})", signature.name),
}
}
fn declarator(ty: Ty, name: &str) -> String {
match ty {
Ty::Scalar(Scalar::Pointer) => format!("void *{name}"),
_ => format!("{} {name}", ty.spelling()),
}
}
fn banner(out: &mut String, what: &str) {
let _ = writeln!(out, "/* {what}");
for line in [
"",
"Generated by `cargo run -q -p rucc-targets -- abi-signatures --write`. Do not edit",
"this file, edit the grammar in `build-tools/rucc-targets/src/signatures.rs`.",
"",
"This is the differential ABI harness of `spec/cross-compile/14-testing.md` section",
"14.3. `callee.c` is compiled by one compiler and `caller.c` by the other, the two are",
"linked together and the program is run, so a value that arrives wrong is the two",
"compilers disagreeing about a calling convention rather than about a layout.",
"",
"The program prints nothing and exits zero when the two agree. Every disagreement",
"prints the function and the parameter it is about, and the exit status is one.",
] {
if line.is_empty() {
out.push_str(" *\n");
} else {
let _ = writeln!(out, " * {line}");
}
}
out.push_str(" */\n\n");
}
fn reason(out: &mut String, why: &str) {
if why.is_empty() {
return;
}
out.push_str("/* ");
let mut column = 3;
for word in why.split_whitespace() {
if column + word.len() > 96 {
out.push_str("\n * ");
column = 3;
} else if column > 3 {
out.push(' ');
column += 1;
}
out.push_str(word);
column += word.len();
}
out.push_str(" */\n");
}
fn header(signatures: &[Signature]) -> String {
let mut out = String::new();
banner(&mut out, "The types and the prototypes both sides of the differential agree on.");
out.push_str("#ifndef RUCC_ABI_SIGNATURES_H\n#define RUCC_ABI_SIGNATURES_H\n\n");
for aggregate in AGGREGATES {
reason(&mut out, aggregate.why);
let keyword = match aggregate.kind {
Kind::Struct => "struct",
Kind::Union => "union",
};
let _ = writeln!(out, "{keyword} {} {{", aggregate.name);
for member in aggregate.members {
match member {
Member::Scalar(name, scalar) => {
let _ = writeln!(out, "\t{};", declarator(Ty::Scalar(*scalar), name));
}
Member::Nested(name, inner) => {
let _ = writeln!(out, "\t{};", declarator(Ty::Aggregate(*inner), name));
}
}
}
out.push_str("};\n\n");
}
out.push_str(
"/* The bytes a pointer argument points into, defined in report.c. Nothing reads them:\n\
\x20* what travels is the address, and an address inside a known object is one both\n\
\x20* sides can name without either of them having to agree about a number. */\n",
);
let _ = writeln!(out, "extern char anchor[{ANCHOR}];\n");
out.push_str(
"/* How many disagreements have been seen, and how one is recorded. Both live in\n\
\x20* report.c, which is the only file here that includes a libc header and is always\n\
\x20* built by the reference compiler. Two `const char *` and a `void` return is the one\n\
\x20* signature every ABI in the table agrees about, so calling this is not itself a\n\
\x20* thing the corpus can get wrong. */\n",
);
out.push_str("extern int abi_failures;\n");
out.push_str("void abi_fail(const char *fn, const char *slot);\n\n");
for signature in signatures {
reason(&mut out, signature.why);
let _ = writeln!(out, "{};", prototype(signature));
if !signature.why.is_empty() {
out.push('\n');
}
}
out.push_str("\n#endif\n");
out
}
fn report() -> String {
let mut out = String::new();
banner(&mut out, "The failure counter, and the only libc call in the corpus.");
out.push_str("#include <stdio.h>\n\n");
out.push_str("#include \"abi.h\"\n\n");
let _ = writeln!(out, "char anchor[{ANCHOR}];");
out.push_str("int abi_failures;\n\n");
out.push_str("void abi_fail(const char *fn, const char *slot)\n{\n");
out.push_str("\tabi_failures++;\n");
out.push_str("\tfprintf(stderr, \"%s: %s did not arrive\\n\", fn, slot);\n");
out.push_str("}\n");
out
}
fn callee(signatures: &[Signature]) -> String {
let mut out = String::new();
banner(&mut out, "The definitions, which check what arrived and return what is expected.");
out.push_str("#include \"abi.h\"\n\n");
for signature in signatures {
let _ = writeln!(out, "{}\n{{", prototype(signature));
for param in &signature.params {
for ((path, _), value) in param.ty.leaves().iter().zip(¶m.values) {
let slot = format!("{}{path}", param.name);
let _ = writeln!(
out,
"\tif ({slot} != {value})\n\t\tabi_fail(\"{}\", \"{slot}\");",
signature.name
);
}
}
if let Some(ty) = signature.ret {
let mut next = 0;
let initializer = ty.initializer(&signature.ret_values, &mut next);
match ty {
Ty::Scalar(_) => {
let _ = writeln!(out, "\treturn {initializer};");
}
Ty::Aggregate(_) => {
let _ = writeln!(out, "\t{} = {initializer};", declarator(ty, "r"));
out.push_str("\treturn r;\n");
}
}
}
out.push_str("}\n\n");
}
out
}
fn caller(signatures: &[Signature]) -> String {
let mut out = String::new();
banner(&mut out, "The calls, which pass what the callee expects and check what came back.");
out.push_str("#include \"abi.h\"\n\n");
out.push_str("int main(void)\n{\n");
for signature in signatures {
out.push_str("\t{\n");
let mut arguments = Vec::new();
for param in &signature.params {
match param.ty {
Ty::Scalar(_) => arguments.push(param.values[0].clone()),
Ty::Aggregate(_) => {
let mut next = 0;
let initializer = param.ty.initializer(¶m.values, &mut next);
let _ =
writeln!(out, "\t\t{} = {initializer};", declarator(param.ty, ¶m.name));
arguments.push(param.name.clone());
}
}
}
let call = format!("{}({})", signature.name, arguments.join(", "));
match signature.ret {
None => {
let _ = writeln!(out, "\t\t{call};");
}
Some(ty) => {
let _ = writeln!(out, "\t\t{} = {call};", declarator(ty, "r"));
for ((path, _), value) in ty.leaves().iter().zip(&signature.ret_values) {
let _ = writeln!(
out,
"\t\tif (r{path} != {value})\n\t\t\tabi_fail(\"{}\", \"return{path}\");",
signature.name
);
}
}
}
out.push_str("\t}\n");
}
out.push_str("\treturn abi_failures == 0 ? 0 : 1;\n}\n");
out
}