Skip to main content

hypothalamus/
target.rs

1//! Target profiles for hosted and freestanding Brainfuck output.
2//!
3//! A target profile is deliberately small: it names an LLVM target triple,
4//! runtime ABI, default output kind, optional target image format, and extra
5//! LLVM-driver flags. Complete image construction stays in target-specific
6//! builder modules.
7
8use crate::driver::EmitKind;
9use crate::llvm::{FreestandingOptions, Runtime};
10
11/// Runtime ABI used by a target profile.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum RuntimeAbi {
14    /// Hosted C ABI with `main`, `putchar`, and `getchar`.
15    Hosted,
16
17    /// Freestanding ABI with caller-supplied byte I/O hooks.
18    Freestanding(FreestandingOptions),
19}
20
21impl RuntimeAbi {
22    /// Return true when this ABI is freestanding.
23    pub fn is_freestanding(&self) -> bool {
24        matches!(self, Self::Freestanding(_))
25    }
26
27    /// Convert this target ABI into LLVM backend runtime options.
28    pub fn to_llvm_runtime(&self) -> Runtime {
29        match self {
30            Self::Hosted => Runtime::Hosted,
31            Self::Freestanding(options) => Runtime::Freestanding(options.clone()),
32        }
33    }
34}
35
36/// Runtime ABI kind used by static target presets.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum RuntimeAbiKind {
39    /// Hosted C ABI.
40    Hosted,
41
42    /// Freestanding byte I/O hook ABI.
43    Freestanding,
44}
45
46impl RuntimeAbiKind {
47    fn into_abi(self) -> RuntimeAbi {
48        match self {
49            Self::Hosted => RuntimeAbi::Hosted,
50            Self::Freestanding => RuntimeAbi::Freestanding(FreestandingOptions::default()),
51        }
52    }
53}
54
55/// Complete-image format produced by a target-specific builder.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum TargetImageFormat {
58    /// Game Boy Advance `.gba` ROM image.
59    Gba,
60}
61
62/// A built-in target preset.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct TargetPreset {
65    /// Stable CLI/API target name.
66    pub name: &'static str,
67
68    /// Short human-readable description.
69    pub description: &'static str,
70
71    /// LLVM target triple embedded in IR and passed to `clang`.
72    pub llvm_triple: Option<&'static str>,
73
74    /// Runtime ABI used by this preset.
75    pub runtime_abi: RuntimeAbiKind,
76
77    /// Additional arguments passed to `clang`.
78    pub clang_args: &'static [&'static str],
79
80    /// Default output kind for this target.
81    pub default_emit: EmitKind,
82
83    /// Optional complete-image format for this target.
84    pub image_format: Option<TargetImageFormat>,
85}
86
87impl TargetPreset {
88    /// Convert this preset into an owned target profile.
89    pub fn profile(self) -> TargetProfile {
90        TargetProfile {
91            name: self.name.to_string(),
92            description: self.description.to_string(),
93            llvm_triple: self.llvm_triple.map(ToString::to_string),
94            runtime_abi: self.runtime_abi.into_abi(),
95            clang_args: self.clang_args.iter().map(ToString::to_string).collect(),
96            default_emit: self.default_emit,
97            image_format: self.image_format,
98        }
99    }
100}
101
102/// Resolved target configuration used by the compiler driver.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct TargetProfile {
105    name: String,
106    description: String,
107    llvm_triple: Option<String>,
108    runtime_abi: RuntimeAbi,
109    clang_args: Vec<String>,
110    default_emit: EmitKind,
111    image_format: Option<TargetImageFormat>,
112}
113
114impl TargetProfile {
115    /// Return the hosted native target profile.
116    pub fn native() -> Self {
117        known_targets()[0].profile()
118    }
119
120    /// Resolve a known target name or treat `value` as a raw LLVM triple.
121    pub fn resolve(value: &str) -> Self {
122        find_target(value)
123            .map(TargetPreset::profile)
124            .unwrap_or_else(|| Self::raw_llvm_triple(value))
125    }
126
127    /// Create a hosted profile for an arbitrary LLVM triple.
128    pub fn raw_llvm_triple(triple: &str) -> Self {
129        Self {
130            name: triple.to_string(),
131            description: "raw LLVM target triple".to_string(),
132            llvm_triple: Some(triple.to_string()),
133            runtime_abi: RuntimeAbi::Hosted,
134            clang_args: Vec::new(),
135            default_emit: EmitKind::Executable,
136            image_format: None,
137        }
138    }
139
140    /// Create a fully custom target profile.
141    pub fn custom(
142        name: impl Into<String>,
143        description: impl Into<String>,
144        llvm_triple: Option<String>,
145        runtime_abi: RuntimeAbi,
146        clang_args: Vec<String>,
147        default_emit: EmitKind,
148    ) -> Self {
149        Self {
150            name: name.into(),
151            description: description.into(),
152            llvm_triple,
153            runtime_abi,
154            clang_args,
155            default_emit,
156            image_format: None,
157        }
158    }
159
160    /// Return the profile name.
161    pub fn name(&self) -> &str {
162        &self.name
163    }
164
165    /// Return the profile description.
166    pub fn description(&self) -> &str {
167        &self.description
168    }
169
170    /// Return the LLVM target triple, if this profile forces one.
171    pub fn llvm_triple(&self) -> Option<&str> {
172        self.llvm_triple.as_deref()
173    }
174
175    /// Return the target runtime ABI.
176    pub fn runtime_abi(&self) -> &RuntimeAbi {
177        &self.runtime_abi
178    }
179
180    /// Return true when this target uses the freestanding ABI.
181    pub fn is_freestanding(&self) -> bool {
182        self.runtime_abi.is_freestanding()
183    }
184
185    /// Return additional arguments passed to `clang` for this target.
186    pub fn clang_args(&self) -> &[String] {
187        &self.clang_args
188    }
189
190    /// Return the default output kind for this target.
191    pub fn default_emit(&self) -> EmitKind {
192        self.default_emit
193    }
194
195    /// Return the complete-image format supported by this profile, if any.
196    pub fn image_format(&self) -> Option<TargetImageFormat> {
197        self.image_format
198    }
199
200    /// Replace the runtime ABI for this profile.
201    pub fn set_runtime_abi(&mut self, runtime_abi: RuntimeAbi) {
202        self.runtime_abi = runtime_abi;
203        if self.runtime_abi.is_freestanding() && self.default_emit == EmitKind::Executable {
204            self.default_emit = EmitKind::Object;
205        }
206    }
207
208    /// Return a copy of this profile with a different runtime ABI.
209    pub fn with_runtime_abi(mut self, runtime_abi: RuntimeAbi) -> Self {
210        self.set_runtime_abi(runtime_abi);
211        self
212    }
213
214    /// Return a copy of this profile with a complete-image format.
215    pub fn with_image_format(mut self, image_format: Option<TargetImageFormat>) -> Self {
216        self.image_format = image_format;
217        self
218    }
219}
220
221/// Return all built-in target presets.
222pub fn known_targets() -> &'static [TargetPreset] {
223    &TARGETS
224}
225
226/// Find a built-in target preset by name.
227pub fn find_target(name: &str) -> Option<TargetPreset> {
228    known_targets()
229        .iter()
230        .copied()
231        .find(|target| target.name == name)
232}
233
234const TARGETS: [TargetPreset; 5] = [
235    TargetPreset {
236        name: "native",
237        description: "hosted executable/JIT on the host LLVM default target",
238        llvm_triple: None,
239        runtime_abi: RuntimeAbiKind::Hosted,
240        clang_args: &[],
241        default_emit: EmitKind::Executable,
242        image_format: None,
243    },
244    TargetPreset {
245        name: "x86_64-none",
246        description: "x86_64 freestanding object for a caller-provided runtime",
247        llvm_triple: Some("x86_64-unknown-none"),
248        runtime_abi: RuntimeAbiKind::Freestanding,
249        clang_args: &[],
250        default_emit: EmitKind::Object,
251        image_format: None,
252    },
253    TargetPreset {
254        name: "i386-none",
255        description: "32-bit x86 freestanding object for tiny boot/runtime layers",
256        llvm_triple: Some("i386-unknown-none"),
257        runtime_abi: RuntimeAbiKind::Freestanding,
258        clang_args: &[],
259        default_emit: EmitKind::Object,
260        image_format: None,
261    },
262    TargetPreset {
263        name: "nds-arm9",
264        description: "Nintendo DS ARM9 freestanding payload object",
265        llvm_triple: Some("armv5te-none-eabi"),
266        runtime_abi: RuntimeAbiKind::Freestanding,
267        clang_args: &["-mcpu=arm946e-s"],
268        default_emit: EmitKind::Object,
269        image_format: None,
270    },
271    TargetPreset {
272        name: "gba",
273        description: "Game Boy Advance ARM7TDMI/Thumb ROM image",
274        llvm_triple: Some("thumbv4t-none-eabi"),
275        runtime_abi: RuntimeAbiKind::Freestanding,
276        clang_args: &["-mcpu=arm7tdmi", "-mthumb"],
277        default_emit: EmitKind::Image,
278        image_format: Some(TargetImageFormat::Gba),
279    },
280];
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn resolves_known_native_target() {
288        let target = TargetProfile::resolve("native");
289
290        assert_eq!(target.name(), "native");
291        assert_eq!(target.llvm_triple(), None);
292        assert!(!target.is_freestanding());
293        assert_eq!(target.default_emit(), EmitKind::Executable);
294    }
295
296    #[test]
297    fn resolves_raw_triples_as_hosted_targets() {
298        let target = TargetProfile::resolve("x86_64-unknown-linux-gnu");
299
300        assert_eq!(target.name(), "x86_64-unknown-linux-gnu");
301        assert_eq!(target.llvm_triple(), Some("x86_64-unknown-linux-gnu"));
302        assert!(!target.is_freestanding());
303        assert_eq!(target.default_emit(), EmitKind::Executable);
304    }
305
306    #[test]
307    fn gba_is_freestanding_thumb_target() {
308        let target = TargetProfile::resolve("gba");
309
310        assert_eq!(target.llvm_triple(), Some("thumbv4t-none-eabi"));
311        assert!(target.is_freestanding());
312        assert_eq!(target.default_emit(), EmitKind::Image);
313        assert_eq!(target.image_format(), Some(TargetImageFormat::Gba));
314        assert!(target.clang_args().iter().any(|arg| arg == "-mthumb"));
315    }
316
317    #[test]
318    fn nds_arm9_is_freestanding_object_target() {
319        let target = TargetProfile::resolve("nds-arm9");
320
321        assert_eq!(target.llvm_triple(), Some("armv5te-none-eabi"));
322        assert!(target.is_freestanding());
323        assert_eq!(target.default_emit(), EmitKind::Object);
324        assert_eq!(target.image_format(), None);
325        assert!(
326            target
327                .clang_args()
328                .iter()
329                .any(|arg| arg == "-mcpu=arm946e-s")
330        );
331    }
332
333    #[test]
334    fn builds_custom_freestanding_target() {
335        let target = TargetProfile::custom(
336            "weird-board",
337            "custom board",
338            Some("thumbv7em-none-eabi".to_string()),
339            RuntimeAbi::Freestanding(FreestandingOptions::default()),
340            vec!["-mcpu=cortex-m4".to_string()],
341            EmitKind::Object,
342        );
343
344        assert_eq!(target.name(), "weird-board");
345        assert_eq!(target.llvm_triple(), Some("thumbv7em-none-eabi"));
346        assert!(target.is_freestanding());
347        assert_eq!(target.clang_args(), ["-mcpu=cortex-m4"]);
348    }
349}