Skip to main content

lamina_platform/
environment.rs

1//! The C runtime and ABI a target means.
2//!
3//! The fourth axis of a target, beside the architecture, the operating system and the
4//! sub-architecture variant. It is what decides the ELF interpreter, the C runtime
5//! objects, the libc and the compiler runtime, so that `s390x_linux` *declares* glibc
6//! rather than inheriting it from whichever cross compiler the build machine has.
7
8use std::{fmt, str::FromStr};
9
10/// The C library an environment links.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum Libc {
13    Glibc,
14    Musl,
15    /// Lamina's own C library. The name is reserved and the plumbing is in place; no
16    /// implementation exists yet, so [`Libc::is_implemented`] answers `false` for it.
17    Lamina,
18}
19
20impl Libc {
21    /// Component of a cross-toolchain triple that names this C library.
22    pub const fn triple_component(self) -> &'static str {
23        match self {
24            Self::Glibc => "gnu",
25            Self::Musl => "musl",
26            Self::Lamina => "lamina",
27        }
28    }
29
30    /// Whether lamina has an implementation of this C library to link a program against.
31    ///
32    /// `false` is a promise that the link refuses and names the library, rather than
33    /// taking another C library's interpreter, start-up objects and symbols instead.
34    pub const fn is_implemented(self) -> bool {
35        match self {
36            Self::Glibc | Self::Musl => true,
37            Self::Lamina => false,
38        }
39    }
40
41    /// Why this C library cannot be linked, for a diagnostic, or `None` when it can.
42    pub const fn unavailable_reason(self) -> Option<&'static str> {
43        match self {
44            Self::Glibc | Self::Musl => None,
45            Self::Lamina => Some(
46                "the Lamina C library is not implemented yet: there is no Lamina libc to link, no start-up objects and no dynamic loader for it",
47            ),
48        }
49    }
50}
51
52impl fmt::Display for Libc {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        let s = match self {
55            Self::Glibc => "glibc",
56            Self::Musl => "musl",
57            Self::Lamina => "lamina",
58        };
59        write!(f, "{s}")
60    }
61}
62
63/// Where the compiler-runtime helpers (`__divti3` and friends) come from.
64///
65/// A separate choice from the [`Libc`]: a musl toolchain links the host compiler runtime
66/// too. Another runtime arrives as one more value here.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub enum CompilerRuntime {
69    /// Lamina's own `builtins/`, compiled per program for the target. It has both the
70    /// 128-bit helpers and the 64-bit ones a target with 32-bit integer registers needs,
71    /// so no host compiler runtime is required beside it.
72    LaminaBuiltins,
73    /// No compiler runtime is linked; a call to a helper is a link error.
74    None,
75}
76
77/// The C runtime and ABI a target means.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79pub enum TargetEnvironment {
80    /// glibc, with the architecture's own floating-point ABI.
81    Gnu,
82    /// musl libc.
83    Musl,
84    /// Lamina's own C library, beside musl rather than on top of it.
85    ///
86    /// Reserved: the axis carries the choice so the target grammar is settled, but no
87    /// Lamina libc has been written. Every environment-derived property that would name
88    /// a file answers `None` for it, and a link refuses by name.
89    Lamina,
90    /// ARM EABI with the soft-float calling convention.
91    Eabi,
92    /// ARM EABI with the hard-float calling convention: floats in VFP registers.
93    Eabihf,
94    /// No libc and no compiler runtime: bare metal, or an operating system that supplies
95    /// its own runtime and leaves nothing for this axis to choose.
96    Freestanding,
97}
98
99impl TargetEnvironment {
100    /// Every environment a target string may name, in the order the diagnostics list them.
101    pub const ALL: [Self; 6] = [
102        Self::Gnu,
103        Self::Musl,
104        Self::Lamina,
105        Self::Eabi,
106        Self::Eabihf,
107        Self::Freestanding,
108    ];
109
110    /// Spelling in a target string.
111    pub const fn token(self) -> &'static str {
112        match self {
113            Self::Gnu => "gnu",
114            Self::Musl => "musl",
115            Self::Lamina => "lamina",
116            Self::Eabi => "eabi",
117            Self::Eabihf => "eabihf",
118            Self::Freestanding => "none",
119        }
120    }
121
122    /// The C library this environment links, or `None` when it links none.
123    pub const fn libc(self) -> Option<Libc> {
124        match self {
125            Self::Gnu | Self::Eabi | Self::Eabihf => Some(Libc::Glibc),
126            Self::Musl => Some(Libc::Musl),
127            Self::Lamina => Some(Libc::Lamina),
128            Self::Freestanding => None,
129        }
130    }
131
132    /// Where the 128-bit integer helpers and the other compiler-runtime symbols come from.
133    pub const fn compiler_runtime(self) -> CompilerRuntime {
134        match self {
135            Self::Gnu | Self::Musl | Self::Lamina | Self::Eabi | Self::Eabihf => {
136                CompilerRuntime::LaminaBuiltins
137            }
138            Self::Freestanding => CompilerRuntime::None,
139        }
140    }
141
142    /// Whether the calling convention passes floating-point values in floating-point
143    /// registers. This is the eabi/eabihf distinction on 32-bit ARM.
144    pub const fn hardware_float(self) -> bool {
145        match self {
146            Self::Gnu | Self::Musl | Self::Lamina | Self::Eabihf => true,
147            Self::Eabi | Self::Freestanding => false,
148        }
149    }
150
151    /// Whether C runtime objects and a libc are linked at all.
152    pub const fn is_hosted(self) -> bool {
153        self.libc().is_some()
154    }
155}
156
157impl fmt::Display for TargetEnvironment {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(f, "{}", self.token())
160    }
161}
162
163impl FromStr for TargetEnvironment {
164    type Err = &'static str;
165
166    fn from_str(s: &str) -> Result<Self, Self::Err> {
167        match s {
168            "gnu" => Ok(Self::Gnu),
169            "musl" => Ok(Self::Musl),
170            "lamina" => Ok(Self::Lamina),
171            "eabi" => Ok(Self::Eabi),
172            "eabihf" => Ok(Self::Eabihf),
173            "none" => Ok(Self::Freestanding),
174            _ => Err("Unknown target environment"),
175        }
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn tokens_round_trip() {
185        for env in TargetEnvironment::ALL {
186            assert_eq!(env.token().parse(), Ok(env));
187            assert_eq!(env.to_string(), env.token());
188        }
189        assert!("glibc".parse::<TargetEnvironment>().is_err());
190    }
191
192    #[test]
193    fn freestanding_links_neither_libc_nor_a_compiler_runtime() {
194        assert_eq!(TargetEnvironment::Freestanding.libc(), None);
195        assert_eq!(
196            TargetEnvironment::Freestanding.compiler_runtime(),
197            CompilerRuntime::None
198        );
199        assert!(!TargetEnvironment::Freestanding.is_hosted());
200    }
201
202    #[test]
203    fn musl_takes_the_same_compiler_runtime_as_gnu() {
204        assert_eq!(TargetEnvironment::Musl.libc(), Some(Libc::Musl));
205        assert_eq!(
206            TargetEnvironment::Musl.compiler_runtime(),
207            CompilerRuntime::LaminaBuiltins
208        );
209        assert_eq!(
210            TargetEnvironment::Gnu.compiler_runtime(),
211            CompilerRuntime::LaminaBuiltins
212        );
213        assert_eq!(Libc::Musl.triple_component(), "musl");
214        assert_eq!(Libc::Glibc.triple_component(), "gnu");
215    }
216
217    #[test]
218    fn every_hosted_environment_selects_our_own_compiler_runtime() {
219        for env in TargetEnvironment::ALL {
220            let expected = if env.is_hosted() {
221                CompilerRuntime::LaminaBuiltins
222            } else {
223                CompilerRuntime::None
224            };
225            assert_eq!(env.compiler_runtime(), expected, "{env}");
226        }
227    }
228
229    #[test]
230    fn lamina_is_a_libc_of_its_own_taking_our_own_compiler_runtime() {
231        let env = TargetEnvironment::Lamina;
232        assert_eq!(env.token(), "lamina");
233        assert_eq!(env.libc(), Some(Libc::Lamina));
234        assert_eq!(env.compiler_runtime(), CompilerRuntime::LaminaBuiltins);
235        assert!(env.is_hosted());
236        assert!(env.hardware_float());
237        assert_eq!(Libc::Lamina.triple_component(), "lamina");
238        assert_eq!(Libc::Lamina.to_string(), "lamina");
239    }
240
241    #[test]
242    fn the_lamina_libc_is_the_one_that_is_not_implemented_yet() {
243        assert!(Libc::Glibc.is_implemented());
244        assert!(Libc::Musl.is_implemented());
245        assert!(!Libc::Lamina.is_implemented());
246        assert_eq!(Libc::Glibc.unavailable_reason(), None);
247        assert_eq!(Libc::Musl.unavailable_reason(), None);
248        let reason = Libc::Lamina
249            .unavailable_reason()
250            .expect("an unimplemented libc states why");
251        assert!(reason.contains("Lamina C library"), "{reason}");
252        assert!(reason.contains("not implemented"), "{reason}");
253    }
254
255    #[test]
256    fn an_unimplemented_libc_is_still_a_hosted_environment() {
257        for env in TargetEnvironment::ALL {
258            let hosted = env.libc().is_some();
259            assert_eq!(env.is_hosted(), hosted, "{env}");
260        }
261    }
262
263    #[test]
264    fn only_eabi_and_freestanding_are_soft_float() {
265        assert!(!TargetEnvironment::Eabi.hardware_float());
266        assert!(!TargetEnvironment::Freestanding.hardware_float());
267        assert!(TargetEnvironment::Eabihf.hardware_float());
268        assert!(TargetEnvironment::Gnu.hardware_float());
269        assert!(TargetEnvironment::Musl.hardware_float());
270    }
271}