use crate::driver::EmitKind;
use crate::llvm::{FreestandingOptions, Runtime};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RuntimeAbi {
Hosted,
Freestanding(FreestandingOptions),
}
impl RuntimeAbi {
pub fn is_freestanding(&self) -> bool {
matches!(self, Self::Freestanding(_))
}
pub fn to_llvm_runtime(&self) -> Runtime {
match self {
Self::Hosted => Runtime::Hosted,
Self::Freestanding(options) => Runtime::Freestanding(options.clone()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuntimeAbiKind {
Hosted,
Freestanding,
}
impl RuntimeAbiKind {
fn into_abi(self) -> RuntimeAbi {
match self {
Self::Hosted => RuntimeAbi::Hosted,
Self::Freestanding => RuntimeAbi::Freestanding(FreestandingOptions::default()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetImageFormat {
Gba,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TargetPreset {
pub name: &'static str,
pub description: &'static str,
pub llvm_triple: Option<&'static str>,
pub runtime_abi: RuntimeAbiKind,
pub clang_args: &'static [&'static str],
pub default_emit: EmitKind,
pub image_format: Option<TargetImageFormat>,
}
impl TargetPreset {
pub fn profile(self) -> TargetProfile {
TargetProfile {
name: self.name.to_string(),
description: self.description.to_string(),
llvm_triple: self.llvm_triple.map(ToString::to_string),
runtime_abi: self.runtime_abi.into_abi(),
clang_args: self.clang_args.iter().map(ToString::to_string).collect(),
default_emit: self.default_emit,
image_format: self.image_format,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TargetProfile {
name: String,
description: String,
llvm_triple: Option<String>,
runtime_abi: RuntimeAbi,
clang_args: Vec<String>,
default_emit: EmitKind,
image_format: Option<TargetImageFormat>,
}
impl TargetProfile {
pub fn native() -> Self {
known_targets()[0].profile()
}
pub fn resolve(value: &str) -> Self {
find_target(value)
.map(TargetPreset::profile)
.unwrap_or_else(|| Self::raw_llvm_triple(value))
}
pub fn raw_llvm_triple(triple: &str) -> Self {
Self {
name: triple.to_string(),
description: "raw LLVM target triple".to_string(),
llvm_triple: Some(triple.to_string()),
runtime_abi: RuntimeAbi::Hosted,
clang_args: Vec::new(),
default_emit: EmitKind::Executable,
image_format: None,
}
}
pub fn custom(
name: impl Into<String>,
description: impl Into<String>,
llvm_triple: Option<String>,
runtime_abi: RuntimeAbi,
clang_args: Vec<String>,
default_emit: EmitKind,
) -> Self {
Self {
name: name.into(),
description: description.into(),
llvm_triple,
runtime_abi,
clang_args,
default_emit,
image_format: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn description(&self) -> &str {
&self.description
}
pub fn llvm_triple(&self) -> Option<&str> {
self.llvm_triple.as_deref()
}
pub fn runtime_abi(&self) -> &RuntimeAbi {
&self.runtime_abi
}
pub fn is_freestanding(&self) -> bool {
self.runtime_abi.is_freestanding()
}
pub fn clang_args(&self) -> &[String] {
&self.clang_args
}
pub fn default_emit(&self) -> EmitKind {
self.default_emit
}
pub fn image_format(&self) -> Option<TargetImageFormat> {
self.image_format
}
pub fn set_runtime_abi(&mut self, runtime_abi: RuntimeAbi) {
self.runtime_abi = runtime_abi;
if self.runtime_abi.is_freestanding() && self.default_emit == EmitKind::Executable {
self.default_emit = EmitKind::Object;
}
}
pub fn with_runtime_abi(mut self, runtime_abi: RuntimeAbi) -> Self {
self.set_runtime_abi(runtime_abi);
self
}
pub fn with_image_format(mut self, image_format: Option<TargetImageFormat>) -> Self {
self.image_format = image_format;
self
}
}
pub fn known_targets() -> &'static [TargetPreset] {
&TARGETS
}
pub fn find_target(name: &str) -> Option<TargetPreset> {
known_targets()
.iter()
.copied()
.find(|target| target.name == name)
}
const TARGETS: [TargetPreset; 5] = [
TargetPreset {
name: "native",
description: "hosted executable/JIT on the host LLVM default target",
llvm_triple: None,
runtime_abi: RuntimeAbiKind::Hosted,
clang_args: &[],
default_emit: EmitKind::Executable,
image_format: None,
},
TargetPreset {
name: "x86_64-none",
description: "x86_64 freestanding object for a caller-provided runtime",
llvm_triple: Some("x86_64-unknown-none"),
runtime_abi: RuntimeAbiKind::Freestanding,
clang_args: &[],
default_emit: EmitKind::Object,
image_format: None,
},
TargetPreset {
name: "i386-none",
description: "32-bit x86 freestanding object for tiny boot/runtime layers",
llvm_triple: Some("i386-unknown-none"),
runtime_abi: RuntimeAbiKind::Freestanding,
clang_args: &[],
default_emit: EmitKind::Object,
image_format: None,
},
TargetPreset {
name: "nds-arm9",
description: "Nintendo DS ARM9 freestanding payload object",
llvm_triple: Some("armv5te-none-eabi"),
runtime_abi: RuntimeAbiKind::Freestanding,
clang_args: &["-mcpu=arm946e-s"],
default_emit: EmitKind::Object,
image_format: None,
},
TargetPreset {
name: "gba",
description: "Game Boy Advance ARM7TDMI/Thumb ROM image",
llvm_triple: Some("thumbv4t-none-eabi"),
runtime_abi: RuntimeAbiKind::Freestanding,
clang_args: &["-mcpu=arm7tdmi", "-mthumb"],
default_emit: EmitKind::Image,
image_format: Some(TargetImageFormat::Gba),
},
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_known_native_target() {
let target = TargetProfile::resolve("native");
assert_eq!(target.name(), "native");
assert_eq!(target.llvm_triple(), None);
assert!(!target.is_freestanding());
assert_eq!(target.default_emit(), EmitKind::Executable);
}
#[test]
fn resolves_raw_triples_as_hosted_targets() {
let target = TargetProfile::resolve("x86_64-unknown-linux-gnu");
assert_eq!(target.name(), "x86_64-unknown-linux-gnu");
assert_eq!(target.llvm_triple(), Some("x86_64-unknown-linux-gnu"));
assert!(!target.is_freestanding());
assert_eq!(target.default_emit(), EmitKind::Executable);
}
#[test]
fn gba_is_freestanding_thumb_target() {
let target = TargetProfile::resolve("gba");
assert_eq!(target.llvm_triple(), Some("thumbv4t-none-eabi"));
assert!(target.is_freestanding());
assert_eq!(target.default_emit(), EmitKind::Image);
assert_eq!(target.image_format(), Some(TargetImageFormat::Gba));
assert!(target.clang_args().iter().any(|arg| arg == "-mthumb"));
}
#[test]
fn nds_arm9_is_freestanding_object_target() {
let target = TargetProfile::resolve("nds-arm9");
assert_eq!(target.llvm_triple(), Some("armv5te-none-eabi"));
assert!(target.is_freestanding());
assert_eq!(target.default_emit(), EmitKind::Object);
assert_eq!(target.image_format(), None);
assert!(
target
.clang_args()
.iter()
.any(|arg| arg == "-mcpu=arm946e-s")
);
}
#[test]
fn builds_custom_freestanding_target() {
let target = TargetProfile::custom(
"weird-board",
"custom board",
Some("thumbv7em-none-eabi".to_string()),
RuntimeAbi::Freestanding(FreestandingOptions::default()),
vec!["-mcpu=cortex-m4".to_string()],
EmitKind::Object,
);
assert_eq!(target.name(), "weird-board");
assert_eq!(target.llvm_triple(), Some("thumbv7em-none-eabi"));
assert!(target.is_freestanding());
assert_eq!(target.clang_args(), ["-mcpu=cortex-m4"]);
}
}