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