#![doc(html_root_url = "https://docs.rs/rucc-target/0.2.16")]
use std::fmt;
use std::str::FromStr;
use rucc_base::float::Format;
mod abi;
pub use crate::abi::{Arg, Call, Kind, Pass, Piece, Scalar, Shape, Slot};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Arch {
X86_64,
Aarch64,
Riscv64,
}
impl Arch {
pub const fn pointer_width(self) -> u32 {
match self {
Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => 64,
}
}
pub const fn is_little_endian(self) -> bool {
match self {
Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => true,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Arch::X86_64 => "x86_64",
Arch::Aarch64 => "aarch64",
Arch::Riscv64 => "riscv64",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Os {
Linux,
Darwin,
Windows,
None,
}
impl Os {
pub const fn as_str(self) -> &'static str {
match self {
Os::Linux => "linux",
Os::Darwin => "darwin",
Os::Windows => "windows",
Os::None => "none",
}
}
pub const fn object_format(self) -> ObjectFormat {
match self {
Os::Linux | Os::None => ObjectFormat::Elf,
Os::Darwin => ObjectFormat::MachO,
Os::Windows => ObjectFormat::Coff,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Env {
None,
Gnu,
Musl,
Msvc,
}
impl Env {
pub const fn as_str(self) -> &'static str {
match self {
Env::None => "none",
Env::Gnu => "gnu",
Env::Musl => "musl",
Env::Msvc => "msvc",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ObjectFormat {
Elf,
MachO,
Coff,
}
impl ObjectFormat {
pub const fn as_str(self) -> &'static str {
match self {
ObjectFormat::Elf => "elf",
ObjectFormat::MachO => "macho",
ObjectFormat::Coff => "coff",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Triple {
pub arch: Arch,
pub os: Os,
pub env: Env,
}
impl Triple {
pub const fn new(arch: Arch, os: Os, env: Env) -> Self {
Self { arch, os, env }
}
pub fn host() -> Option<Self> {
let arch = match std::env::consts::ARCH {
"x86_64" => Arch::X86_64,
"aarch64" => Arch::Aarch64,
"riscv64" => Arch::Riscv64,
_ => return None,
};
let linux = if cfg!(target_env = "musl") { Env::Musl } else { Env::Gnu };
let (os, env) = match std::env::consts::OS {
"linux" => (Os::Linux, linux),
"macos" => (Os::Darwin, Env::None),
"windows" => (Os::Windows, Env::Msvc),
_ => return None,
};
Some(Self::new(arch, os, env))
}
}
impl fmt::Display for Triple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-unknown-{}-{}", self.arch.as_str(), self.os.as_str(), self.env.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseTripleError {
pub input: String,
pub reason: &'static str,
}
impl fmt::Display for ParseTripleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unsupported target triple `{}`: {}", self.input, self.reason)
}
}
impl std::error::Error for ParseTripleError {}
impl FromStr for Triple {
type Err = ParseTripleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let err = |reason| ParseTripleError { input: s.to_owned(), reason };
let mut parts = s.split('-');
let arch = match parts.next() {
Some("x86_64" | "amd64") => Arch::X86_64,
Some("aarch64" | "arm64") => Arch::Aarch64,
Some("riscv64") => Arch::Riscv64,
_ => return Err(err("unknown architecture")),
};
let rest: Vec<&str> = parts.collect();
let mut os = None;
let mut env = None;
for part in &rest {
match *part {
"linux" => os = Some(Os::Linux),
"darwin" | "macos" | "macosx" | "ios" => os = Some(Os::Darwin),
"windows" | "win32" => os = Some(Os::Windows),
"none" if os.is_none() => os = Some(Os::None),
"none" => env = Some(Env::None),
"elf" => os = os.or(Some(Os::None)),
"gnu" | "gnueabi" | "gnueabihf" => env = Some(Env::Gnu),
"musl" | "musleabi" | "musleabihf" => env = Some(Env::Musl),
"msvc" => env = Some(Env::Msvc),
_ => {}
}
}
let os = os.ok_or_else(|| err("unknown operating system"))?;
let env = env.unwrap_or(match os {
Os::Linux => Env::Gnu,
Os::Windows => Env::Msvc,
Os::Darwin | Os::None => Env::None,
});
Ok(Self::new(arch, os, env))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct TargetInfo {
pub triple: Triple,
pub pointer_width: u32,
pub little_endian: bool,
pub char_is_signed: bool,
pub long_width: u32,
pub long_double_width: u32,
pub long_double_format: Format,
pub float64x_format: Format,
pub wchar_width: u32,
pub wchar_is_signed: bool,
pub bit_int_granule: u32,
pub object_format: ObjectFormat,
pub va_list: VaList,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VaList {
CharPointer,
VoidPointer,
SysV,
Aapcs,
}
impl VaList {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
VaList::CharPointer => "char-pointer",
VaList::VoidPointer => "void-pointer",
VaList::SysV => "sysv",
VaList::Aapcs => "aapcs",
}
}
}
impl TargetInfo {
pub fn new(triple: Triple) -> Self {
let char_is_signed = match (triple.arch, triple.os) {
(Arch::Aarch64 | Arch::Riscv64, Os::Linux | Os::None) => false,
_ => true,
};
let long_width = match triple.os {
Os::Windows => 32,
_ => triple.arch.pointer_width(),
};
let long_double_width = match triple.os {
Os::Darwin | Os::Windows => 64,
Os::Linux | Os::None => 128,
};
let long_double_format = match (triple.arch, long_double_width) {
(_, 64) => Format::Double,
(Arch::X86_64, _) => Format::X87Extended,
(Arch::Aarch64 | Arch::Riscv64, _) => Format::Quad,
};
let float64x_format = match triple.arch {
Arch::X86_64 => Format::X87Extended,
Arch::Aarch64 | Arch::Riscv64 => Format::Quad,
};
let bit_int_granule = match triple.arch {
Arch::Aarch64 => 128,
Arch::X86_64 | Arch::Riscv64 => 64,
};
let wchar_width = if triple.os == Os::Windows { 16 } else { 32 };
let wchar_is_signed = !matches!(
(triple.arch, triple.os),
(_, Os::Windows) | (Arch::Aarch64, Os::Linux | Os::None)
);
let va_list = match (triple.arch, triple.os) {
(_, Os::Windows) | (Arch::Aarch64, Os::Darwin) => VaList::CharPointer,
(Arch::X86_64, _) => VaList::SysV,
(Arch::Aarch64, _) => VaList::Aapcs,
(Arch::Riscv64, _) => VaList::VoidPointer,
};
Self {
triple,
pointer_width: triple.arch.pointer_width(),
little_endian: triple.arch.is_little_endian(),
char_is_signed,
long_width,
long_double_width,
long_double_format,
float64x_format,
wchar_width,
wchar_is_signed,
bit_int_granule,
object_format: triple.os.object_format(),
va_list,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_a_four_field_triple() {
let t: Triple = "x86_64-unknown-linux-gnu".parse().unwrap();
assert_eq!(t, Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
}
#[test]
fn parses_a_triple_with_no_vendor() {
let t: Triple = "aarch64-linux-musl".parse().unwrap();
assert_eq!(t, Triple::new(Arch::Aarch64, Os::Linux, Env::Musl));
}
#[test]
fn accepts_the_common_aliases() {
let a: Triple = "arm64-apple-darwin".parse().unwrap();
let b: Triple = "aarch64-apple-darwin".parse().unwrap();
assert_eq!(a, b);
assert_eq!(a.env, Env::None);
}
#[test]
fn fills_in_the_default_environment() {
let t: Triple = "x86_64-unknown-linux".parse().unwrap();
assert_eq!(t.env, Env::Gnu);
let w: Triple = "x86_64-pc-windows".parse().unwrap();
assert_eq!(w.env, Env::Msvc);
}
#[test]
fn rejects_what_it_does_not_support() {
let e = "sparc64-unknown-linux-gnu".parse::<Triple>().unwrap_err();
assert_eq!(e.reason, "unknown architecture");
let e = "x86_64-unknown-plan9".parse::<Triple>().unwrap_err();
assert_eq!(e.reason, "unknown operating system");
}
#[test]
fn displays_in_a_normalised_form() {
let t: Triple = "amd64-linux-gnu".parse().unwrap();
assert_eq!(t.to_string(), "x86_64-unknown-linux-gnu");
}
#[test]
fn display_round_trips_through_parse() {
for s in [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-darwin-none",
"riscv64-unknown-linux-musl",
] {
let t: Triple = s.parse().unwrap();
assert_eq!(t.to_string().parse::<Triple>().unwrap(), t);
}
}
#[test]
fn char_signedness_follows_the_psabi() {
let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
assert!(x86.char_is_signed);
assert!(!arm.char_is_signed);
assert!(mac.char_is_signed, "Apple overrides AAPCS64 back to a signed char");
}
#[test]
fn windows_is_llp64() {
let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
assert_eq!(win.pointer_width, 64);
assert_eq!(win.long_width, 32);
}
#[test]
fn apple_long_double_is_double() {
let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
assert_eq!(mac.long_double_width, 64);
assert_eq!(mac.long_double_format, Format::Double);
let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
assert_eq!(linux.long_double_width, 128);
}
#[test]
fn wchar_t_divides_the_targets_in_two_directions_at_once() {
let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
assert_eq!((windows.wchar_width, windows.wchar_is_signed), (16, false));
let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
assert_eq!((arm.wchar_width, arm.wchar_is_signed), (32, false));
let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
assert_eq!((linux.wchar_width, linux.wchar_is_signed), (32, true));
let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
assert_eq!((mac.wchar_width, mac.wchar_is_signed), (32, true));
}
#[test]
fn va_list_is_the_psabis_type_and_not_one_type_with_four_spellings() {
let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
assert_eq!(linux.va_list, VaList::SysV);
let mac = TargetInfo::new("x86_64-apple-darwin".parse().unwrap());
assert_eq!(mac.va_list, VaList::SysV);
let arm_mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
assert_eq!(arm_mac.va_list, VaList::CharPointer);
let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
assert_eq!(arm.va_list, VaList::Aapcs);
let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
assert_eq!(win.va_list, VaList::CharPointer);
let arm_win = TargetInfo::new("aarch64-pc-windows-msvc".parse().unwrap());
assert_eq!(arm_win.va_list, VaList::CharPointer);
let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
assert_eq!(riscv.va_list, VaList::VoidPointer);
}
#[test]
fn two_targets_agree_on_the_width_of_long_double_and_not_on_the_type() {
let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
assert_eq!(x86.long_double_width, arm.long_double_width);
assert_eq!(x86.long_double_format, Format::X87Extended);
assert_eq!(arm.long_double_format, Format::Quad);
assert_eq!(x86.long_double_format.precision(), 64);
assert_eq!(arm.long_double_format.precision(), 113);
let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
assert_eq!(windows.long_double_format, Format::Double);
}
#[test]
fn float64x_follows_the_processor_where_long_double_follows_the_operating_system() {
let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
assert_eq!(x86.float64x_format, Format::X87Extended);
let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
assert_eq!(arm.float64x_format, Format::Quad);
let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
assert_eq!(riscv.float64x_format, Format::Quad);
let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
assert_eq!(mac.long_double_format, Format::Double);
assert_eq!(mac.float64x_format, Format::Quad);
let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
assert_eq!(windows.long_double_format, Format::Double);
assert_eq!(windows.float64x_format, Format::X87Extended);
}
#[test]
fn the_object_format_follows_the_operating_system() {
assert_eq!(Os::Linux.object_format(), ObjectFormat::Elf);
assert_eq!(Os::Darwin.object_format(), ObjectFormat::MachO);
assert_eq!(Os::Windows.object_format(), ObjectFormat::Coff);
}
#[test]
fn the_host_triple_is_one_we_support() {
let host = Triple::host().expect("the host must be a supported target");
assert_eq!(host.to_string().parse::<Triple>().unwrap(), host);
}
}