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 DRAWN_VARIADIC: usize = 24;
const ANCHOR: u64 = 64;
const QUAD: &str = "defined(__FLT128_MANT_DIG__) && defined(__x86_64__)";
const INT128: &str = "defined(__SIZEOF_INT128__) && defined(__x86_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,
Float128,
Int128,
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::Float128 => "_Float128",
Scalar::Int128 => "__int128",
Scalar::Pointer => "void *",
}
}
fn promoted(self) -> Scalar {
match self {
Scalar::Char | Scalar::SChar | Scalar::UChar | Scalar::Short | Scalar::UShort => {
Scalar::Int
}
Scalar::Float => Scalar::Double,
other => other,
}
}
}
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,
guard: Option<&'static str>,
why: &'static str,
members: &'static [Member],
}
const AGGREGATES: &[Aggregate] = &[
Aggregate {
name: "one_char",
kind: Kind::Struct,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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,
guard: None,
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)],
},
Aggregate {
name: "quad_one",
kind: Kind::Struct,
guard: Some(QUAD),
why: "One _Float128 in a struct, which SysV calls an eightbyte of SSE and an eightbyte of \
SSEUP and puts in a single vector register. It is the only shape here that spends \
two eightbytes on one register, so a compiler that places arguments by counting \
eightbytes gets everything behind it wrong and gets nothing else wrong.",
members: &[Member::Scalar("x", Scalar::Float128)],
},
Aggregate {
name: "quad_two",
kind: Kind::Struct,
guard: Some(QUAD),
why: "Two of them, which is thirty two bytes and past the limit SysV classifies inside, \
so the whole thing goes to memory although every member of it is a float. AAPCS64 \
reads the same declaration as a homogeneous aggregate and gives it two vector \
registers, which is the sort of disagreement the corpus exists to find.",
members: &[Member::Scalar("x", Scalar::Float128), Member::Scalar("y", Scalar::Float128)],
},
Aggregate {
name: "quad_or_long",
kind: Kind::Union,
guard: Some(QUAD),
why: "A union of a _Float128 and a long, which is the merge rule and the post merge rules \
in one object: the first eightbyte is a float and an integer at once and comes out \
INTEGER, the second is an SSEUP with no SSE in front of it any more and is turned \
back into SSE, so sixteen bytes arrive split between the two register files.",
members: &[Member::Scalar("q", Scalar::Float128), Member::Scalar("a", Scalar::Long)],
},
];
#[derive(Clone, Copy, PartialEq, Eq)]
enum Ty {
Scalar(Scalar),
Aggregate(usize),
}
impl Ty {
fn promoted(self) -> Ty {
match self {
Ty::Scalar(scalar) => Ty::Scalar(scalar.promoted()),
Ty::Aggregate(_) => self,
}
}
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,
guard: Option<&'static str>,
why: &'static str,
ret: Option<Ty>,
ret_values: Vec<String>,
params: Vec<Param>,
varargs: Vec<Param>,
}
impl Signature {
fn is_variadic(&self) -> bool {
!self.varargs.is_empty()
}
}
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::Float128 => format!("{}.25f128", 1 + n % 100_000),
Scalar::Int128 => format!(
"(__int128)(((unsigned __int128){}ULL << 64) | {}ULL)",
1 + n * 7919 % 4_000_000_000,
1 + n.wrapping_mul(0x0001_0f2c_3d4e_5f60) % 18_000_000_000_000_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));
}
let mut variadic =
|name: &str, why: &'static str, ret: Option<Ty>, params: Vec<Ty>, varargs: Vec<Ty>| {
out.push(build_variadic(&mut values, name.to_string(), why, ret, params, varargs));
};
variadic(
"hv_ints_past_the_registers",
"Ten integers past the dots, which is more than any ABI here has argument registers, so \
the callee reads some of them out of a register save area and the rest off the stack. \
Where those two meet is the thing va_arg is easiest to get wrong about.",
Some(Ty::Scalar(Scalar::LongLong)),
vec![Ty::Scalar(Scalar::Int)],
vec![Ty::Scalar(Scalar::LongLong); 10],
);
variadic(
"hv_doubles_past_the_registers",
"The same for the other register file, which on SysV is a second save area with a count \
of its own, and on Windows x64 is the general purpose registers because a variadic call \
there puts a double in both.",
Some(Ty::Scalar(Scalar::Double)),
vec![Ty::Scalar(Scalar::Int)],
vec![Ty::Scalar(Scalar::Double); 10],
);
variadic(
"hv_mixed_past_the_registers",
"Integers and doubles alternating past the dots, which is the case where the two save \
areas are being walked at once and each one has its own idea of how far along it is.",
Some(Ty::Scalar(Scalar::Int)),
vec![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),
],
);
variadic(
"hv_promotions",
"The six types no program can pass through a `...`, passed anyway. Everything narrower \
than an int arrives as an int and a float arrives as a double, so the callee reads back \
a type the caller never wrote, and a compiler that skipped the promotion puts two bytes \
where four are read.",
Some(Ty::Scalar(Scalar::Int)),
vec![Ty::Scalar(Scalar::Int)],
vec![
Ty::Scalar(Scalar::Char),
Ty::Scalar(Scalar::SChar),
Ty::Scalar(Scalar::UChar),
Ty::Scalar(Scalar::Short),
Ty::Scalar(Scalar::UShort),
Ty::Scalar(Scalar::Float),
],
);
variadic(
"hv_float_aggregates",
"The homogeneous floating point aggregates past the dots, which is the one place the five \
ABIs described here do not all answer the same way. Darwin arm64 puts every variadic \
argument in the argument area, so the two floats AAPCS64 would give two vector registers \
are on the stack, and a caller that asked the fixed question writes registers the callee \
never reads.",
Some(Ty::Aggregate(5)),
vec![Ty::Scalar(Scalar::Int)],
vec![Ty::Aggregate(4), Ty::Aggregate(5), Ty::Aggregate(6)],
);
variadic(
"hv_small_aggregates",
"The aggregates that fit in registers, past the dots, where the question is whether the \
classification that put them there is the same classification the callee undoes.",
Some(Ty::Aggregate(2)),
vec![Ty::Scalar(Scalar::Int)],
vec![Ty::Aggregate(0), Ty::Aggregate(1), Ty::Aggregate(2), Ty::Aggregate(3)],
);
variadic(
"hv_memory_aggregate",
"The aggregate too large for registers, past the dots, with something either side of it, \
so a callee that walks past the wrong number of bytes reads the next argument.",
Some(Ty::Scalar(Scalar::Int)),
vec![Ty::Scalar(Scalar::Int)],
vec![Ty::Scalar(Scalar::Int), Ty::Aggregate(9), Ty::Scalar(Scalar::Int)],
);
variadic(
"hv_returns_by_hidden_pointer",
"A variadic function returning a large aggregate, which is the two argument shifting \
rules at once: the hidden pointer takes a register before anything else, and everything \
past the dots is placed after that.",
Some(Ty::Aggregate(9)),
vec![Ty::Scalar(Scalar::Int)],
vec![Ty::Scalar(Scalar::Double), Ty::Scalar(Scalar::LongLong)],
);
variadic(
"hv_long_double_and_friends",
"A long double past the dots between two integers, which is the type whose size, \
alignment and format all move between rows, and which the save area has to be aligned \
for wherever it is sixteen bytes.",
None,
vec![Ty::Scalar(Scalar::Int)],
vec![
Ty::Scalar(Scalar::Int),
Ty::Scalar(Scalar::LongDouble),
Ty::Scalar(Scalar::Int),
Ty::Scalar(Scalar::LongDouble),
],
);
variadic(
"hv_registers_already_spent",
"Named parameters that use up the registers before the dots are reached, so every \
variadic argument is on the stack and the save area holds nothing the callee wants. The \
opposite of the case above it, and the one where an off by one in the save area offset \
does not show up.",
Some(Ty::Scalar(Scalar::Int)),
vec![Ty::Scalar(Scalar::LongLong); 8],
vec![Ty::Scalar(Scalar::LongLong), Ty::Scalar(Scalar::Double), Ty::Aggregate(2)],
);
for index in 0..DRAWN_VARIADIC {
let named = 1 + rng.below(3) as usize;
let mut params: Vec<Ty> = (0..named).map(|_| draw(&mut rng)).collect();
let last = params.len() - 1;
params[last] = params[last].promoted();
let count = 1 + rng.below(6) as usize;
let varargs: 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_variadic(&mut values, format!("v{index:02}"), "", ret, params, varargs));
}
let mut quad = |name: &str, why: &'static str, ret: Option<Ty>, params: Vec<Ty>| {
let signature = build(&mut values, name.to_string(), why, ret, params);
out.push(Signature { guard: Some(QUAD), ..signature });
};
quad(
"q_quad_between_ints",
"A _Float128 between two integers and returned as one. The type is sixteen bytes with \
sixteen byte alignment and travels in a vector register on the row this runs on, so the \
integers either side of it are how a shift in either register file shows up.",
Some(Ty::Scalar(Scalar::Float128)),
vec![Ty::Scalar(Scalar::Int), Ty::Scalar(Scalar::Float128), Ty::Scalar(Scalar::Int)],
);
quad(
"q_quads_past_the_registers",
"Ten of them, which is more than SysV has vector registers, so the last two are on the \
stack and are the only arguments in this corpus whose stack slot has to be aligned to \
sixteen rather than to eight.",
Some(Ty::Scalar(Scalar::Float128)),
vec![Ty::Scalar(Scalar::Float128); 10],
);
quad(
"q_struct_then_double",
"The struct holding one quad with a double behind it, which is the case tamnd/rucc#1191 \
was about. The struct is two eightbytes and one register, so a compiler that counts the \
eightbytes hands the double a register the struct is already sitting in.",
Some(Ty::Aggregate(12)),
vec![Ty::Aggregate(12), Ty::Scalar(Scalar::Double), Ty::Scalar(Scalar::Int)],
);
quad(
"q_memory_aggregate",
"The thirty two byte one with an integer either side, which is the aggregate of floats \
that goes to memory anyway, so a caller that left the copy in the wrong place moves the \
argument after it as well.",
Some(Ty::Aggregate(13)),
vec![Ty::Scalar(Scalar::Int), Ty::Aggregate(13), Ty::Scalar(Scalar::Int)],
);
quad(
"q_union_and_quad",
"The union, returned and passed, with a bare quad behind it. The union spends one \
register of each file and the quad behind it spends a second vector register, so this is \
the one case here where the two counters have to move by different amounts for the same \
argument.",
Some(Ty::Aggregate(14)),
vec![Ty::Aggregate(14), Ty::Scalar(Scalar::Float128)],
);
let mut wide = |name: &str, why: &'static str, ret: Option<Ty>, params: Vec<Ty>| {
let signature = build(&mut values, name.to_string(), why, ret, params);
out.push(Signature { guard: Some(INT128), ..signature });
};
wide(
"w_int128_with_one_register_left",
"Five integers and then a __int128, which gets no pair of registers, so the whole of it \
goes in the argument area and the sixth register stays empty. The long long behind it is \
the one that takes that register, which is the half of the rule a compiler that split \
the value into two words gets wrong.",
Some(Ty::Scalar(Scalar::Int128)),
vec![
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::Int128),
Ty::Scalar(Scalar::LongLong),
],
);
wide(
"w_int128_on_a_sixteen_byte_boundary",
"Seven integers and then a __int128, so the seventh is the first word of the argument \
area and the wide value leaves the second one empty to start on a sixteen byte boundary.",
Some(Ty::Scalar(Scalar::Int128)),
vec![
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::LongLong),
Ty::Scalar(Scalar::Int128),
Ty::Scalar(Scalar::Int),
],
);
wide(
"w_int128s_past_the_registers",
"Six of them with a double in the middle, so three are in registers, three are in the \
argument area and the double takes a vector register without moving any of them.",
Some(Ty::Scalar(Scalar::Int128)),
vec![
Ty::Scalar(Scalar::Int128),
Ty::Scalar(Scalar::Int128),
Ty::Scalar(Scalar::Int128),
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::Int128),
Ty::Scalar(Scalar::Int128),
Ty::Scalar(Scalar::Int128),
],
);
let mut quad_variadic =
|name: &str, why: &'static str, ret: Option<Ty>, params: Vec<Ty>, varargs: Vec<Ty>| {
let signature =
build_variadic(&mut values, name.to_string(), why, ret, params, varargs);
out.push(Signature { guard: Some(QUAD), ..signature });
};
quad_variadic(
"qv_quads_past_the_registers",
"Ten quads past the dots, which is the va_arg walk over the one type whose slot in the \
register save area is the whole of a vector register rather than the low half of one. \
Eight of them are in the area and the last two are where the caller left them, so this \
asks about both ends of the walk and about the sixteen byte alignment the argument area \
owes the type.",
Some(Ty::Scalar(Scalar::Float128)),
vec![Ty::Scalar(Scalar::Int)],
vec![Ty::Scalar(Scalar::Float128); 10],
);
quad_variadic(
"qv_quad_between_doubles",
"A quad with a double either side of it past the dots, which is where the offset into the \
vector half has to move by sixteen for one of them and by eight for the others. A walk \
that stepped the counter by the same amount for all three reads the second double out of \
the top of the quad.",
Some(Ty::Scalar(Scalar::Double)),
vec![Ty::Scalar(Scalar::Int)],
vec![
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::Float128),
Ty::Scalar(Scalar::Double),
Ty::Scalar(Scalar::Float128),
Ty::Scalar(Scalar::Double),
],
);
quad_variadic(
"qv_struct_holding_a_quad",
"The struct holding one read off the list, which is the other half of the walk: an \
aggregate that arrived in registers is copied out of the save area into a buffer, and \
this is the only object in the corpus whose one slot is sixteen bytes wide.",
Some(Ty::Aggregate(12)),
vec![Ty::Scalar(Scalar::Int)],
vec![Ty::Aggregate(12), Ty::Scalar(Scalar::Int)],
);
out
}
fn draw(rng: &mut Rng) -> Ty {
if rng.below(3) == 0 {
Ty::Aggregate(rng.below(drawable() as u64) as usize)
} else {
Ty::Scalar(SCALARS[rng.below(SCALARS.len() as u64) as usize])
}
}
fn drawable() -> usize {
AGGREGATES.iter().position(|aggregate| aggregate.guard.is_some()).unwrap_or(AGGREGATES.len())
}
fn build(
values: &mut Values,
name: String,
why: &'static str,
ret: Option<Ty>,
params: Vec<Ty>,
) -> Signature {
build_variadic(values, name, why, ret, params, Vec::new())
}
fn build_variadic(
values: &mut Values,
name: String,
why: &'static str,
ret: Option<Ty>,
params: Vec<Ty>,
varargs: Vec<Ty>,
) -> Signature {
let params: Vec<Param> = params
.into_iter()
.enumerate()
.map(|(index, ty)| Param { name: format!("a{index}"), values: values.for_type(ty), ty })
.collect();
let varargs = varargs
.into_iter()
.enumerate()
.map(|(index, ty)| Param {
name: format!("v{index}"),
values: values.for_type(ty),
ty: ty.promoted(),
})
.collect();
let ret_values = match ret {
Some(ty) => values.for_type(ty),
None => Vec::new(),
};
Signature { name, guard: None, why, ret, ret_values, params, varargs }
}
fn prototype(signature: &Signature) -> String {
let mut params = if signature.params.is_empty() {
"void".to_string()
} else {
signature
.params
.iter()
.map(|param| declarator(param.ty, ¶m.name))
.collect::<Vec<_>>()
.join(", ")
};
if signature.is_variadic() {
params.push_str(", ...");
}
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 open_guard(out: &mut String, guard: Option<&'static str>) {
if let Some(guard) = guard {
let _ = writeln!(out, "#if {guard}");
}
}
fn close_guard(out: &mut String, guard: Option<&'static str>) {
if guard.is_some() {
out.push_str("#endif\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);
open_guard(&mut out, aggregate.guard);
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");
close_guard(&mut out, aggregate.guard);
out.push('\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");
out.push_str(
"/* Reading the arguments past a `...`, spelled with the builtins rather than with\n\
\x20* <stdarg.h>. The two files under test include no libc header, for the reason\n\
\x20* report.c exists, and stdarg.h is the one header a freestanding program is still\n\
\x20* allowed to want. Every compiler this corpus is compiled by implements va_start,\n\
\x20* va_arg and va_end as exactly these builtins, so this is the same header with one\n\
\x20* fewer thing that has to be found on disk. */\n",
);
out.push_str("#define ABI_VA_LIST __builtin_va_list\n");
out.push_str("#define ABI_VA_START(ap, last) __builtin_va_start(ap, last)\n");
out.push_str("#define ABI_VA_ARG(ap, ty) __builtin_va_arg(ap, ty)\n");
out.push_str("#define ABI_VA_END(ap) __builtin_va_end(ap)\n\n");
for signature in signatures {
reason(&mut out, signature.why);
open_guard(&mut out, signature.guard);
let _ = writeln!(out, "{};", prototype(signature));
close_guard(&mut out, signature.guard);
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 {
open_guard(&mut out, signature.guard);
let _ = writeln!(out, "{}\n{{", prototype(signature));
if signature.is_variadic() {
out.push_str("\tABI_VA_LIST ap;\n\n");
}
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 signature.is_variadic() {
let last = signature.params.last().expect("a variadic signature has a named parameter");
let _ = writeln!(out, "\n\tABI_VA_START(ap, {});", last.name);
for param in &signature.varargs {
out.push_str("\t{\n");
let _ = writeln!(
out,
"\t\t{} = ABI_VA_ARG(ap, {});",
declarator(param.ty, ¶m.name),
param.ty.spelling()
);
for ((path, _), value) in param.ty.leaves().iter().zip(¶m.values) {
let slot = format!("{}{path}", param.name);
let _ = writeln!(
out,
"\t\tif ({slot} != {value})\n\t\t\tabi_fail(\"{}\", \"{slot}\");",
signature.name
);
}
out.push_str("\t}\n");
}
out.push_str("\tABI_VA_END(ap);\n\n");
}
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");
close_guard(&mut out, signature.guard);
out.push('\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 {
open_guard(&mut out, signature.guard);
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());
}
}
}
for param in &signature.varargs {
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");
close_guard(&mut out, signature.guard);
}
out.push_str("\treturn abi_failures == 0 ? 0 : 1;\n}\n");
out
}