1use std::borrow::Cow;
7
8use cfg_expr::targets::{
9 Abi, Arch, Env, Families, Family, HasAtomic, HasAtomics, Os, TargetInfo, Triple, Vendor,
10};
11use serde::{Deserialize, Serialize};
12
13#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
14#[serde(rename_all = "kebab-case")]
15pub(crate) struct TargetDefinition {
16 arch: String,
25 #[serde(rename = "target-pointer-width", with = "target_pointer_width")]
26 pointer_width: u8,
27
28 #[allow(dead_code)]
31 llvm_target: String,
32 #[allow(dead_code)]
33 data_layout: String,
34
35 #[serde(default)]
37 os: Option<String>,
38 #[serde(default)]
39 abi: Option<String>,
40 #[serde(default)]
41 env: Option<String>,
42 #[serde(default)]
43 vendor: Option<String>,
44 #[serde(default)]
45 target_family: Vec<String>,
46 #[serde(default)]
47 target_endian: Endian,
48 #[serde(default)]
49 min_atomic_width: Option<u16>,
50 #[serde(default)]
51 max_atomic_width: Option<u16>,
52 #[serde(default)]
53 panic_strategy: PanicStrategy,
54}
55
56impl TargetDefinition {
57 pub(crate) fn into_target_info(self, triple: Cow<'static, str>) -> TargetInfo {
58 let min_atomic_width = self.min_atomic_width.unwrap_or(8);
61 let max_atomic_width = self.max_atomic_width.unwrap_or(self.pointer_width as u16);
63
64 let mut has_atomics = Vec::new();
65 let mut atomic_width = 8;
68 while atomic_width <= max_atomic_width {
69 if atomic_width < min_atomic_width {
70 atomic_width *= 2;
71 continue;
72 }
73 has_atomics.push(HasAtomic::IntegerSize(atomic_width));
74 if atomic_width == self.pointer_width as u16 {
75 has_atomics.push(HasAtomic::Pointer);
76 }
77 atomic_width *= 2;
78 }
79
80 TargetInfo {
81 triple: Triple::new(triple),
82 os: self.os.map(Os::new),
83 abi: self.abi.map(Abi::new),
84 arch: Arch::new(self.arch),
85 env: self.env.map(Env::new),
86 vendor: self.vendor.map(Vendor::new),
87 families: Families::new(self.target_family.into_iter().map(Family::new)),
88 pointer_width: self.pointer_width,
89 endian: self.target_endian.to_cfg_expr(),
90 has_atomics: HasAtomics::new(has_atomics),
91 panic: self.panic_strategy.to_cfg_expr(),
92 }
93 }
94}
95
96mod target_pointer_width {
97 use serde::{Deserializer, Serializer};
98
99 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<u8, D::Error>
100 where
101 D: Deserializer<'de>,
102 {
103 use std::fmt;
104
105 struct PointerWidthVisitor;
106
107 impl<'de> serde::de::Visitor<'de> for PointerWidthVisitor {
108 type Value = u8;
109
110 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
111 formatter.write_str("a string or integer representing pointer width")
112 }
113
114 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
115 where
116 E: serde::de::Error,
117 {
118 value
119 .try_into()
120 .map_err(|_| E::custom(format!("pointer width {value} out of range for u8")))
121 }
122
123 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
124 where
125 E: serde::de::Error,
126 {
127 value
128 .parse::<u8>()
129 .map_err(|error| E::custom(format!("error parsing as integer: {error}")))
130 }
131 }
132
133 deserializer.deserialize_any(PointerWidthVisitor)
134 }
135
136 pub(super) fn serialize<S>(value: &u8, serializer: S) -> Result<S::Ok, S::Error>
137 where
138 S: Serializer,
139 {
140 serializer.serialize_str(&value.to_string())
142 }
143}
144
145#[derive(
146 Copy, Clone, Debug, Deserialize, Serialize, Default, Eq, Hash, Ord, PartialEq, PartialOrd,
147)]
148#[serde(rename_all = "kebab-case")]
149enum Endian {
150 #[default]
151 Little,
152 Big,
153}
154
155impl Endian {
156 fn to_cfg_expr(self) -> cfg_expr::targets::Endian {
157 match self {
158 Self::Little => cfg_expr::targets::Endian::little,
159 Self::Big => cfg_expr::targets::Endian::big,
160 }
161 }
162}
163
164#[derive(
165 Copy, Clone, Debug, Deserialize, Serialize, Default, Eq, Hash, Ord, PartialEq, PartialOrd,
166)]
167#[serde(rename_all = "kebab-case")]
168enum PanicStrategy {
169 #[default]
170 Unwind,
171 Abort,
172}
173
174impl PanicStrategy {
175 fn to_cfg_expr(self) -> cfg_expr::targets::Panic {
176 match self {
177 Self::Unwind => cfg_expr::targets::Panic::unwind,
178 Self::Abort => cfg_expr::targets::Panic::abort,
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use std::{collections::BTreeMap, process::Command};
187
188 #[derive(Deserialize)]
189 #[serde(transparent)]
190 struct AllTargets(BTreeMap<String, TargetDefinition>);
191
192 #[test]
193 fn test_all_builtin_specs_recognized() {
194 let rustc_bin: String = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_owned());
195 let output = Command::new(rustc_bin)
196 .env("RUSTC_BOOTSTRAP", "1")
198 .args(["-Z", "unstable-options", "--print", "all-target-specs-json"])
199 .output()
200 .expect("rustc command succeeded");
201 assert!(output.status.success(), "rustc command succeeded");
202
203 let all_targets: AllTargets = serde_json::from_slice(&output.stdout)
204 .expect("deserializing all-target-specs-json succeeded");
205 for (triple, target_def) in all_targets.0 {
206 eprintln!("*** testing {triple}");
207 target_def.clone().into_target_info(triple.clone().into());
210 let json =
211 serde_json::to_string(&target_def).expect("target def serialized successfully");
212 eprintln!("* minified json: {json}");
213 let target_def_2 = serde_json::from_str(&json).expect("target def 2 deserialized");
214 assert_eq!(target_def, target_def_2, "matches");
215
216 if triple.starts_with("powerpc-") || triple.starts_with("powerpc64-") {
218 assert_eq!(
219 target_def.target_endian,
220 Endian::Big,
221 "powerpc is big-endian"
222 );
223 }
224 if triple.contains("-linux") {
225 assert!(
226 target_def.target_family.contains(&"unix".to_owned()),
227 "linux target_family should contain unix (was {:#?})",
228 target_def.target_family,
229 );
230 }
231 }
232 }
233}