gruel_target/
lib.rs

1//! Target architecture and OS definitions for the Gruel compiler.
2//!
3//! This crate provides the `Target` enum and related types that define
4//! compilation targets. It is a leaf crate with no dependencies, designed
5//! to be used by the CLI, compiler, codegen, and linker crates.
6
7use std::fmt;
8use std::str::FromStr;
9
10/// A compilation target consisting of an architecture and operating system.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum Target {
13    /// x86-64 Linux (System V AMD64 ABI)
14    X86_64Linux,
15    /// AArch64 Linux (AAPCS64 ABI)
16    Aarch64Linux,
17    /// AArch64 macOS (Apple Silicon, AAPCS64 with Apple extensions)
18    Aarch64Macos,
19}
20
21impl Target {
22    /// Detect the host target at compile time.
23    ///
24    /// Returns the target that matches the current compilation environment.
25    /// This is useful for defaulting to native compilation.
26    #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
27    pub fn host() -> Self {
28        Target::X86_64Linux
29    }
30
31    #[cfg(all(target_arch = "aarch64", target_os = "linux"))]
32    pub fn host() -> Self {
33        Target::Aarch64Linux
34    }
35
36    #[cfg(all(target_arch = "aarch64", target_os = "macos"))]
37    pub fn host() -> Self {
38        Target::Aarch64Macos
39    }
40
41    #[cfg(not(any(
42        all(target_arch = "x86_64", target_os = "linux"),
43        all(target_arch = "aarch64", target_os = "linux"),
44        all(target_arch = "aarch64", target_os = "macos")
45    )))]
46    pub fn host() -> Self {
47        // For unsupported hosts, default to x86-64 Linux as a reasonable
48        // cross-compilation target. This allows the compiler to be built
49        // and tested on any platform.
50        Target::X86_64Linux
51    }
52
53    /// Returns the architecture component of this target.
54    pub fn arch(&self) -> Arch {
55        match self {
56            Target::X86_64Linux => Arch::X86_64,
57            Target::Aarch64Linux | Target::Aarch64Macos => Arch::Aarch64,
58        }
59    }
60
61    /// Returns the operating system component of this target.
62    pub fn os(&self) -> Os {
63        match self {
64            Target::X86_64Linux | Target::Aarch64Linux => Os::Linux,
65            Target::Aarch64Macos => Os::Macos,
66        }
67    }
68
69    /// Returns the ELF e_machine value for this target, if it uses ELF format.
70    ///
71    /// This is used when generating ELF object files and executables.
72    /// Returns `None` for targets that don't use ELF (e.g., macOS uses Mach-O).
73    pub fn elf_machine(&self) -> Option<u16> {
74        if !self.is_elf() {
75            return None;
76        }
77        match self.arch() {
78            Arch::X86_64 => Some(0x3E),  // EM_X86_64
79            Arch::Aarch64 => Some(0xB7), // EM_AARCH64
80        }
81    }
82
83    /// Returns the default page size for this target in bytes.
84    ///
85    /// This is used for executable segment alignment.
86    pub fn page_size(&self) -> u64 {
87        match self {
88            // x86-64 and AArch64 Linux typically use 4KB pages.
89            Target::X86_64Linux | Target::Aarch64Linux => 0x1000, // 4KB
90            // macOS on Apple Silicon uses 16KB pages.
91            Target::Aarch64Macos => 0x4000, // 16KB
92        }
93    }
94
95    /// Returns the default base address for executables on this target.
96    ///
97    /// This is the virtual address where the executable is loaded.
98    pub fn default_base_addr(&self) -> u64 {
99        match self {
100            // Standard Linux load address for both architectures.
101            Target::X86_64Linux | Target::Aarch64Linux => 0x400000,
102            // macOS uses a different address space layout; the dynamic linker
103            // handles placement. We use a conventional address.
104            Target::Aarch64Macos => 0x100000000,
105        }
106    }
107
108    /// Returns the pointer size in bytes for this target.
109    pub fn pointer_size(&self) -> u32 {
110        match self.arch() {
111            Arch::X86_64 | Arch::Aarch64 => 8, // 64-bit architectures
112        }
113    }
114
115    /// Returns the required stack alignment in bytes for this target.
116    ///
117    /// This is the alignment required at function call boundaries.
118    pub fn stack_alignment(&self) -> u32 {
119        match self {
120            // System V AMD64, AAPCS64, and Apple's ABI all require 16-byte alignment.
121            Target::X86_64Linux | Target::Aarch64Linux | Target::Aarch64Macos => 16,
122        }
123    }
124
125    /// Returns the triple string for this target (e.g., "x86_64-unknown-linux-gnu").
126    ///
127    /// This can be useful for invoking external tools like system linkers.
128    pub fn triple(&self) -> &'static str {
129        match self {
130            Target::X86_64Linux => "x86_64-unknown-linux-gnu",
131            Target::Aarch64Linux => "aarch64-unknown-linux-gnu",
132            Target::Aarch64Macos => "aarch64-apple-darwin",
133        }
134    }
135
136    /// Returns whether this target uses Mach-O object format (macOS).
137    pub fn is_macho(&self) -> bool {
138        matches!(self, Target::Aarch64Macos)
139    }
140
141    /// Returns whether this target uses ELF object format (Linux).
142    pub fn is_elf(&self) -> bool {
143        matches!(self, Target::X86_64Linux | Target::Aarch64Linux)
144    }
145
146    /// Returns the minimum macOS version for this target, encoded for Mach-O.
147    ///
148    /// The version is encoded as `0x00XXYYPP` where XX is major, YY is minor, PP is patch.
149    /// For example, macOS 11.0.0 (Big Sur) is encoded as `0x000B0000`.
150    ///
151    /// Returns `None` for non-macOS targets.
152    ///
153    /// Note: macOS 11.0 (Big Sur) was the first version to support Apple Silicon (ARM64),
154    /// which is why it's the minimum for `Aarch64Macos`.
155    pub fn macos_min_version(&self) -> Option<u32> {
156        match self {
157            Target::Aarch64Macos => Some(0x000B0000), // 11.0.0 (Big Sur)
158            Target::X86_64Linux | Target::Aarch64Linux => None,
159        }
160    }
161
162    /// Returns all supported targets.
163    pub fn all() -> &'static [Target] {
164        &[
165            Target::X86_64Linux,
166            Target::Aarch64Linux,
167            Target::Aarch64Macos,
168        ]
169    }
170
171    /// Returns a comma-separated string of all target names for help text.
172    pub fn all_names() -> &'static str {
173        "x86-64-linux, aarch64-linux, aarch64-macos"
174    }
175}
176
177impl fmt::Display for Target {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        match self {
180            Target::X86_64Linux => write!(f, "x86-64-linux"),
181            Target::Aarch64Linux => write!(f, "aarch64-linux"),
182            Target::Aarch64Macos => write!(f, "aarch64-macos"),
183        }
184    }
185}
186
187/// Error returned when parsing an invalid target string.
188#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct ParseTargetError {
190    input: String,
191}
192
193impl fmt::Display for ParseTargetError {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        write!(
196            f,
197            "unknown target '{}'. Valid targets: {}",
198            self.input,
199            Target::all()
200                .iter()
201                .map(|t| t.to_string())
202                .collect::<Vec<_>>()
203                .join(", ")
204        )
205    }
206}
207
208impl std::error::Error for ParseTargetError {}
209
210impl Default for Target {
211    /// Returns the host target as the default.
212    ///
213    /// This allows code to write `Target::default()` instead of `Target::host()`,
214    /// which is useful for struct initialization with `..Default::default()`.
215    fn default() -> Self {
216        Self::host()
217    }
218}
219
220impl FromStr for Target {
221    type Err = ParseTargetError;
222
223    fn from_str(s: &str) -> Result<Self, Self::Err> {
224        match s {
225            "x86-64-linux" | "x86_64-linux" | "x86_64-unknown-linux-gnu" => Ok(Target::X86_64Linux),
226            "aarch64-linux" | "arm64-linux" | "aarch64-unknown-linux-gnu" => {
227                Ok(Target::Aarch64Linux)
228            }
229            "aarch64-macos" | "arm64-macos" | "aarch64-apple-darwin" => Ok(Target::Aarch64Macos),
230            _ => Err(ParseTargetError {
231                input: s.to_string(),
232            }),
233        }
234    }
235}
236
237/// The CPU architecture of a target.
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239pub enum Arch {
240    /// x86-64 (AMD64)
241    X86_64,
242    /// AArch64 (ARM64)
243    Aarch64,
244}
245
246impl fmt::Display for Arch {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        match self {
249            Arch::X86_64 => write!(f, "x86-64"),
250            Arch::Aarch64 => write!(f, "aarch64"),
251        }
252    }
253}
254
255/// The operating system of a target.
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
257pub enum Os {
258    /// Linux
259    Linux,
260    /// macOS (Darwin)
261    Macos,
262}
263
264impl fmt::Display for Os {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        match self {
267            Os::Linux => write!(f, "linux"),
268            Os::Macos => write!(f, "macos"),
269        }
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_target_parsing() {
279        assert_eq!(
280            "x86-64-linux".parse::<Target>().unwrap(),
281            Target::X86_64Linux
282        );
283        assert_eq!(
284            "x86_64-linux".parse::<Target>().unwrap(),
285            Target::X86_64Linux
286        );
287        assert_eq!(
288            "aarch64-linux".parse::<Target>().unwrap(),
289            Target::Aarch64Linux
290        );
291        assert_eq!(
292            "arm64-linux".parse::<Target>().unwrap(),
293            Target::Aarch64Linux
294        );
295        assert_eq!(
296            "aarch64-macos".parse::<Target>().unwrap(),
297            Target::Aarch64Macos
298        );
299        assert_eq!(
300            "arm64-macos".parse::<Target>().unwrap(),
301            Target::Aarch64Macos
302        );
303        assert_eq!(
304            "aarch64-apple-darwin".parse::<Target>().unwrap(),
305            Target::Aarch64Macos
306        );
307    }
308
309    #[test]
310    fn test_target_display() {
311        assert_eq!(Target::X86_64Linux.to_string(), "x86-64-linux");
312        assert_eq!(Target::Aarch64Linux.to_string(), "aarch64-linux");
313        assert_eq!(Target::Aarch64Macos.to_string(), "aarch64-macos");
314    }
315
316    #[test]
317    fn test_invalid_target() {
318        assert!("windows".parse::<Target>().is_err());
319        assert!("riscv64".parse::<Target>().is_err());
320    }
321
322    #[test]
323    fn test_elf_machine() {
324        assert_eq!(Target::X86_64Linux.elf_machine(), Some(0x3E));
325        assert_eq!(Target::Aarch64Linux.elf_machine(), Some(0xB7));
326        // Mach-O targets return None since they don't use ELF format
327        assert_eq!(Target::Aarch64Macos.elf_machine(), None);
328    }
329
330    #[test]
331    fn test_arch_decomposition() {
332        assert_eq!(Target::X86_64Linux.arch(), Arch::X86_64);
333        assert_eq!(Target::Aarch64Linux.arch(), Arch::Aarch64);
334        assert_eq!(Target::Aarch64Macos.arch(), Arch::Aarch64);
335    }
336
337    #[test]
338    fn test_os_decomposition() {
339        assert_eq!(Target::X86_64Linux.os(), Os::Linux);
340        assert_eq!(Target::Aarch64Linux.os(), Os::Linux);
341        assert_eq!(Target::Aarch64Macos.os(), Os::Macos);
342    }
343
344    #[test]
345    fn test_pointer_size() {
346        assert_eq!(Target::X86_64Linux.pointer_size(), 8);
347        assert_eq!(Target::Aarch64Linux.pointer_size(), 8);
348        assert_eq!(Target::Aarch64Macos.pointer_size(), 8);
349    }
350
351    #[test]
352    fn test_stack_alignment() {
353        assert_eq!(Target::X86_64Linux.stack_alignment(), 16);
354        assert_eq!(Target::Aarch64Linux.stack_alignment(), 16);
355        assert_eq!(Target::Aarch64Macos.stack_alignment(), 16);
356    }
357
358    #[test]
359    fn test_triple() {
360        assert_eq!(Target::X86_64Linux.triple(), "x86_64-unknown-linux-gnu");
361        assert_eq!(Target::Aarch64Linux.triple(), "aarch64-unknown-linux-gnu");
362        assert_eq!(Target::Aarch64Macos.triple(), "aarch64-apple-darwin");
363    }
364
365    #[test]
366    fn test_is_elf_macho() {
367        assert!(Target::X86_64Linux.is_elf());
368        assert!(Target::Aarch64Linux.is_elf());
369        assert!(!Target::Aarch64Macos.is_elf());
370
371        assert!(!Target::X86_64Linux.is_macho());
372        assert!(!Target::Aarch64Linux.is_macho());
373        assert!(Target::Aarch64Macos.is_macho());
374    }
375
376    #[test]
377    fn test_page_size() {
378        assert_eq!(Target::X86_64Linux.page_size(), 0x1000);
379        assert_eq!(Target::Aarch64Linux.page_size(), 0x1000);
380        assert_eq!(Target::Aarch64Macos.page_size(), 0x4000);
381    }
382
383    #[test]
384    fn test_macos_min_version() {
385        // Linux targets return None
386        assert_eq!(Target::X86_64Linux.macos_min_version(), None);
387        assert_eq!(Target::Aarch64Linux.macos_min_version(), None);
388        // macOS returns the encoded version (11.0.0 = 0x000B0000 for Big Sur)
389        assert_eq!(Target::Aarch64Macos.macos_min_version(), Some(0x000B0000));
390    }
391
392    #[test]
393    fn test_default_returns_host() {
394        assert_eq!(Target::default(), Target::host());
395    }
396
397    #[test]
398    fn test_display_from_str_round_trip() {
399        // Verify that Display and FromStr are inverses for all targets
400        for target in Target::all() {
401            let displayed = target.to_string();
402            let parsed: Target = displayed
403                .parse()
404                .expect("Display output should be parseable");
405            assert_eq!(
406                *target, parsed,
407                "Round-trip failed for {}: displayed as '{}', parsed back as {:?}",
408                target, displayed, parsed
409            );
410        }
411    }
412}