xref: /linux/scripts/generate_rust_target.rs (revision 500580c7ae7bbd38c7641102059fa9308f35d26c)
1  // SPDX-License-Identifier: GPL-2.0
2  
3  //! The custom target specification file generator for `rustc`.
4  //!
5  //! To configure a target from scratch, a JSON-encoded file has to be passed
6  //! to `rustc` (introduced in [RFC 131]). These options and the file itself are
7  //! unstable. Eventually, `rustc` should provide a way to do this in a stable
8  //! manner. For instance, via command-line arguments. Therefore, this file
9  //! should avoid using keys which can be set via `-C` or `-Z` options.
10  //!
11  //! [RFC 131]: https://rust-lang.github.io/rfcs/0131-target-specification.html
12  
13  use std::{
14      collections::HashMap,
15      fmt::{Display, Formatter, Result},
16      io::BufRead,
17  };
18  
19  enum Value {
20      Boolean(bool),
21      Number(i32),
22      String(String),
23      Array(Vec<Value>),
24      Object(Object),
25  }
26  
27  type Object = Vec<(String, Value)>;
28  
29  fn comma_sep<T>(
30      seq: &[T],
31      formatter: &mut Formatter<'_>,
32      f: impl Fn(&mut Formatter<'_>, &T) -> Result,
33  ) -> Result {
34      if let [ref rest @ .., ref last] = seq[..] {
35          for v in rest {
36              f(formatter, v)?;
37              formatter.write_str(",")?;
38          }
39          f(formatter, last)?;
40      }
41      Ok(())
42  }
43  
44  /// Minimal "almost JSON" generator (e.g. no `null`s, no escaping),
45  /// enough for this purpose.
46  impl Display for Value {
47      fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
48          match self {
49              Value::Boolean(boolean) => write!(formatter, "{}", boolean),
50              Value::Number(number) => write!(formatter, "{}", number),
51              Value::String(string) => write!(formatter, "\"{}\"", string),
52              Value::Array(values) => {
53                  formatter.write_str("[")?;
54                  comma_sep(&values[..], formatter, |formatter, v| v.fmt(formatter))?;
55                  formatter.write_str("]")
56              }
57              Value::Object(object) => {
58                  formatter.write_str("{")?;
59                  comma_sep(&object[..], formatter, |formatter, v| {
60                      write!(formatter, "\"{}\": {}", v.0, v.1)
61                  })?;
62                  formatter.write_str("}")
63              }
64          }
65      }
66  }
67  
68  impl From<bool> for Value {
69      fn from(value: bool) -> Self {
70          Self::Boolean(value)
71      }
72  }
73  
74  impl From<i32> for Value {
75      fn from(value: i32) -> Self {
76          Self::Number(value)
77      }
78  }
79  
80  impl From<String> for Value {
81      fn from(value: String) -> Self {
82          Self::String(value)
83      }
84  }
85  
86  impl From<&str> for Value {
87      fn from(value: &str) -> Self {
88          Self::String(value.to_string())
89      }
90  }
91  
92  impl From<Object> for Value {
93      fn from(object: Object) -> Self {
94          Self::Object(object)
95      }
96  }
97  
98  impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
99      fn from(i: [T; N]) -> Self {
100          Self::Array(i.into_iter().map(|v| v.into()).collect())
101      }
102  }
103  
104  struct TargetSpec(Object);
105  
106  impl TargetSpec {
107      fn new() -> TargetSpec {
108          TargetSpec(Vec::new())
109      }
110  
111      fn push(&mut self, key: &str, value: impl Into<Value>) {
112          self.0.push((key.to_string(), value.into()));
113      }
114  }
115  
116  impl Display for TargetSpec {
117      fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
118          // We add some newlines for clarity.
119          formatter.write_str("{\n")?;
120          if let [ref rest @ .., ref last] = self.0[..] {
121              for (key, value) in rest {
122                  write!(formatter, "    \"{}\": {},\n", key, value)?;
123              }
124              write!(formatter, "    \"{}\": {}\n", last.0, last.1)?;
125          }
126          formatter.write_str("}")
127      }
128  }
129  
130  struct KernelConfig(HashMap<String, String>);
131  
132  impl KernelConfig {
133      /// Parses `include/config/auto.conf` from `stdin`.
134      fn from_stdin() -> KernelConfig {
135          let mut result = HashMap::new();
136  
137          let stdin = std::io::stdin();
138          let mut handle = stdin.lock();
139          let mut line = String::new();
140  
141          loop {
142              line.clear();
143  
144              if handle.read_line(&mut line).unwrap() == 0 {
145                  break;
146              }
147  
148              if line.starts_with('#') {
149                  continue;
150              }
151  
152              let (key, value) = line.split_once('=').expect("Missing `=` in line.");
153              result.insert(key.to_string(), value.trim_end_matches('\n').to_string());
154          }
155  
156          KernelConfig(result)
157      }
158  
159      /// Does the option exist in the configuration (any value)?
160      ///
161      /// The argument must be passed without the `CONFIG_` prefix.
162      /// This avoids repetition and it also avoids `fixdep` making us
163      /// depend on it.
164      fn has(&self, option: &str) -> bool {
165          let option = "CONFIG_".to_owned() + option;
166          self.0.contains_key(&option)
167      }
168  }
169  
170  fn main() {
171      let cfg = KernelConfig::from_stdin();
172      let mut ts = TargetSpec::new();
173  
174      // `llvm-target`s are taken from `scripts/Makefile.clang`.
175      if cfg.has("ARM64") {
176          panic!("arm64 uses the builtin rustc aarch64-unknown-none target");
177      } else if cfg.has("RISCV") {
178          if cfg.has("64BIT") {
179              panic!("64-bit RISC-V uses the builtin rustc riscv64-unknown-none-elf target");
180          } else {
181              panic!("32-bit RISC-V is an unsupported architecture");
182          }
183      } else if cfg.has("X86_64") {
184          ts.push("arch", "x86_64");
185          ts.push(
186              "data-layout",
187              "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
188          );
189          let mut features = "-mmx,+soft-float".to_string();
190          if cfg.has("MITIGATION_RETPOLINE") {
191              // The kernel uses `-mretpoline-external-thunk` (for Clang), which Clang maps to the
192              // target feature of the same name plus the other two target features in
193              // `clang/lib/Driver/ToolChains/Arch/X86.cpp`. These should be eventually enabled via
194              // `-Ctarget-feature` when `rustc` starts recognizing them (or via a new dedicated
195              // flag); see https://github.com/rust-lang/rust/issues/116852.
196              features += ",+retpoline-external-thunk";
197              features += ",+retpoline-indirect-branches";
198              features += ",+retpoline-indirect-calls";
199          }
200          if cfg.has("MITIGATION_SLS") {
201              // The kernel uses `-mharden-sls=all`, which Clang maps to both these target features in
202              // `clang/lib/Driver/ToolChains/Arch/X86.cpp`. These should be eventually enabled via
203              // `-Ctarget-feature` when `rustc` starts recognizing them (or via a new dedicated
204              // flag); see https://github.com/rust-lang/rust/issues/116851.
205              features += ",+harden-sls-ijmp";
206              features += ",+harden-sls-ret";
207          }
208          ts.push("features", features);
209          ts.push("llvm-target", "x86_64-linux-gnu");
210          ts.push("supported-sanitizers", ["kcfi", "kernel-address"]);
211          ts.push("target-pointer-width", "64");
212      } else if cfg.has("X86_32") {
213          // This only works on UML, as i386 otherwise needs regparm support in rustc
214          if !cfg.has("UML") {
215              panic!("32-bit x86 only works under UML");
216          }
217          ts.push("arch", "x86");
218          ts.push(
219              "data-layout",
220              "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
221          );
222          let mut features = "-mmx,+soft-float".to_string();
223          if cfg.has("MITIGATION_RETPOLINE") {
224              features += ",+retpoline-external-thunk";
225          }
226          ts.push("features", features);
227          ts.push("llvm-target", "i386-unknown-linux-gnu");
228          ts.push("target-pointer-width", "32");
229      } else if cfg.has("LOONGARCH") {
230          panic!("loongarch uses the builtin rustc loongarch64-unknown-none-softfloat target");
231      } else {
232          panic!("Unsupported architecture");
233      }
234  
235      ts.push("emit-debug-gdb-scripts", false);
236      ts.push("frame-pointer", "may-omit");
237      ts.push(
238          "stack-probes",
239          vec![("kind".to_string(), Value::String("none".to_string()))],
240      );
241  
242      // Everything else is LE, whether `CPU_LITTLE_ENDIAN` is declared or not
243      // (e.g. x86). It is also `rustc`'s default.
244      if cfg.has("CPU_BIG_ENDIAN") {
245          ts.push("target-endian", "big");
246      }
247  
248      println!("{}", ts);
249  }
250