Skip to main content

rucc_tuple/
abi.rs

1//! The calling convention variant and the object format.
2
3use core::fmt;
4
5use crate::{Arch, DataModel, Env, Os, SubArch};
6
7/// The variant of the platform ABI that decides where floating point arguments go.
8///
9/// This is a separate field from the environment because on ARM it is genuinely orthogonal to
10/// the libc, and because it is the clearest case of `spec/cross-compile/03-target-model.md`'s rule: it changes
11/// how a function is called, so it is in the tuple. GCC fuses it into the environment component
12/// as `gnueabihf`, and [`crate::TargetTuple`] reproduces that spelling on output rather than
13/// storing the fused form.
14///
15/// The names are the ones the RISC-V and LoongArch `-mabi=` flags use, minus the data model
16/// prefix that those flags carry redundantly. `-mabi=lp64d` is
17/// [`DataModel::Lp64`](crate::DataModel::Lp64) plus [`Abi::DoubleFloat`], and splitting it means
18/// the data model is written down once.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
20pub enum Abi {
21    /// The psABI default for this target. Resolved by [`Abi::resolve`], which is what code
22    /// generation should call rather than matching on this variant.
23    #[default]
24    Default,
25    /// Floating point arguments in integer registers, and no FPU assumed. `-mfloat-abi=soft`,
26    /// `-mabi=lp64`, `-mabi=ilp32`.
27    SoftFloat,
28    /// Floating point arguments in integer registers, but FPU instructions are emitted for
29    /// arithmetic. ARM's `softfp`, which is ABI compatible with soft float and faster.
30    SoftFp,
31    /// Single precision floating point arguments in float registers. `-mabi=lp64f`. RISC-V
32    /// only; no target in the matrix uses it, and it exists because leaving it out would make
33    /// the enumeration a lie about the ABI space.
34    SingleFloat,
35    /// Double precision floating point arguments in float registers. `-mfloat-abi=hard`,
36    /// `-mabi=lp64d`, `-mabi=ilp32d`.
37    DoubleFloat,
38}
39
40impl Abi {
41    /// The name used in diagnostics, in `--print-config` and in `-mabi=` reconstruction.
42    pub const fn as_str(self) -> &'static str {
43        match self {
44            Abi::Default => "default",
45            Abi::SoftFloat => "soft",
46            Abi::SoftFp => "softfp",
47            Abi::SingleFloat => "single",
48            Abi::DoubleFloat => "hard",
49        }
50    }
51
52    /// The concrete ABI for a target that did not name one.
53    ///
54    /// Every case here is a psABI reading rather than a preference. AArch64, x86-64 and Darwin
55    /// have one float ABI so there is nothing to choose. RISC-V and LoongArch Linux are LP64D by
56    /// convention and by every distribution's build. Bare metal ARM is soft float because the
57    /// core may have no FPU, and ARM Linux is hard float because every ARM distribution shipping
58    /// today is, which is why the matrix rows are all `eabihf`.
59    pub const fn resolve(self, arch: Arch, sub_arch: SubArch, os: Os, env: Env) -> Abi {
60        if !matches!(self, Abi::Default) {
61            return self;
62        }
63        match arch {
64            Arch::Arm => {
65                if sub_arch.is_thumb_only() || matches!(os, Os::None) {
66                    Abi::SoftFloat
67                } else if matches!(env, Env::Gnu | Env::Musl | Env::Android) {
68                    Abi::DoubleFloat
69                } else {
70                    Abi::SoftFloat
71                }
72            }
73            Arch::Riscv64 | Arch::Riscv32 | Arch::LoongArch64 => match os {
74                Os::None => Abi::SoftFloat,
75                _ => Abi::DoubleFloat,
76            },
77            _ => Abi::DoubleFloat,
78        }
79    }
80
81    /// Whether this ABI may be named for this architecture.
82    ///
83    /// Naming a float ABI on x86-64 is a spelling error rather than a configuration, because
84    /// SysV AMD64 has one convention and there is nothing to select. Saying so is the whole
85    /// point of validating the tuple: the alternative is accepting the flag and ignoring it.
86    pub const fn is_valid_for(self, arch: Arch) -> bool {
87        match self {
88            Abi::Default => true,
89            Abi::SoftFp => matches!(arch, Arch::Arm),
90            Abi::SingleFloat => matches!(arch, Arch::Riscv64 | Arch::Riscv32),
91            Abi::SoftFloat | Abi::DoubleFloat => arch.selects_float_abi(),
92        }
93    }
94
95    /// The `-mabi=` value GCC would take for this ABI on this architecture, which is what the
96    /// driver has to reproduce when it hands work to an external assembler or linker.
97    pub fn to_mabi(self, arch: Arch, model: DataModel) -> Option<String> {
98        let resolved = self;
99        match arch {
100            Arch::Riscv64 | Arch::Riscv32 | Arch::LoongArch64 => {
101                let base = match model {
102                    DataModel::Lp64 => "lp64",
103                    DataModel::Ilp32 | DataModel::Ilp32On64 => "ilp32",
104                    DataModel::Llp64 => return None,
105                };
106                let suffix = match resolved {
107                    Abi::SoftFloat => "",
108                    Abi::SingleFloat => "f",
109                    Abi::DoubleFloat => "d",
110                    Abi::Default | Abi::SoftFp => return None,
111                };
112                Some(format!("{base}{suffix}"))
113            }
114            _ => None,
115        }
116    }
117}
118
119impl fmt::Display for Abi {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        f.write_str(self.as_str())
122    }
123}
124
125/// The container the compiler writes.
126///
127/// One per OS, with freestanding taking its format from the architecture. rucc writes all four
128/// itself rather than handing text to an assembler, which is why this is a first class field:
129/// every one of them is a different section model, a different relocation table and a different
130/// symbol table.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
132pub enum ObjectFormat {
133    /// ELF. Linux, the BSDs, illumos, and freestanding on every architecture but wasm.
134    Elf,
135    /// Mach-O. Darwin, and the format with a version treadmill: chained fixups and
136    /// `__init_offsets` are recent and are not optional on current systems.
137    MachO,
138    /// COFF, in its PE flavour. Windows, both environments, with unwind information in
139    /// `.pdata` and `.xdata` rather than in a DWARF section.
140    Coff,
141    /// The WebAssembly object format. A different shape from the other three, and
142    /// `spec/cross-compile/05-architectures.md` section 5.9 treats reaching it as a second back end.
143    Wasm,
144}
145
146impl ObjectFormat {
147    /// The name used in diagnostics and in `--print-config`.
148    pub const fn as_str(self) -> &'static str {
149        match self {
150            ObjectFormat::Elf => "elf",
151            ObjectFormat::MachO => "macho",
152            ObjectFormat::Coff => "coff",
153            ObjectFormat::Wasm => "wasm",
154        }
155    }
156
157    /// The suffix an object file gets on this format.
158    pub const fn object_extension(self) -> &'static str {
159        match self {
160            ObjectFormat::Coff => "obj",
161            _ => "o",
162        }
163    }
164
165    /// The suffix a static archive gets.
166    pub const fn archive_extension(self) -> &'static str {
167        match self {
168            ObjectFormat::Coff => "lib",
169            _ => "a",
170        }
171    }
172
173    /// Whether symbols are prefixed with an underscore.
174    ///
175    /// True on Mach-O and on 32-bit COFF, false on ELF and 64-bit COFF. Getting this wrong
176    /// produces a link error naming a symbol that is visibly present in the object, which is one
177    /// of the more confusing ways to spend an afternoon.
178    pub const fn leading_underscore(self, data_model: DataModel) -> bool {
179        match self {
180            ObjectFormat::MachO => true,
181            ObjectFormat::Coff => matches!(data_model, DataModel::Ilp32),
182            ObjectFormat::Elf | ObjectFormat::Wasm => false,
183        }
184    }
185}
186
187impl fmt::Display for ObjectFormat {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        f.write_str(self.as_str())
190    }
191}