linux/scripts/generate_rust_target.rs

210 lines
6.5 KiB
Rust
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
//! The custom target specification file generator for `rustc`.
//!
//! To configure a target from scratch, a JSON-encoded file has to be passed
//! to `rustc` (introduced in [RFC 131]). These options and the file itself are
//! unstable. Eventually, `rustc` should provide a way to do this in a stable
//! manner. For instance, via command-line arguments. Therefore, this file
//! should avoid using keys which can be set via `-C` or `-Z` options.
//!
//! [RFC 131]: https://rust-lang.github.io/rfcs/0131-target-specification.html
use std::{
collections::HashMap,
fmt::{Display, Formatter, Result},
io::BufRead,
};
enum Value {
Boolean(bool),
Number(i32),
String(String),
Object(Object),
}
type Object = Vec<(String, Value)>;
/// Minimal "almost JSON" generator (e.g. no `null`s, no arrays, no escaping),
/// enough for this purpose.
impl Display for Value {
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
match self {
Value::Boolean(boolean) => write!(formatter, "{}", boolean),
Value::Number(number) => write!(formatter, "{}", number),
Value::String(string) => write!(formatter, "\"{}\"", string),
Value::Object(object) => {
formatter.write_str("{")?;
if let [ref rest @ .., ref last] = object[..] {
for (key, value) in rest {
write!(formatter, "\"{}\": {},", key, value)?;
}
write!(formatter, "\"{}\": {}", last.0, last.1)?;
}
formatter.write_str("}")
}
}
}
}
struct TargetSpec(Object);
impl TargetSpec {
fn new() -> TargetSpec {
TargetSpec(Vec::new())
}
}
trait Push<T> {
fn push(&mut self, key: &str, value: T);
}
impl Push<bool> for TargetSpec {
fn push(&mut self, key: &str, value: bool) {
self.0.push((key.to_string(), Value::Boolean(value)));
}
}
impl Push<i32> for TargetSpec {
fn push(&mut self, key: &str, value: i32) {
self.0.push((key.to_string(), Value::Number(value)));
}
}
impl Push<String> for TargetSpec {
fn push(&mut self, key: &str, value: String) {
self.0.push((key.to_string(), Value::String(value)));
}
}
impl Push<&str> for TargetSpec {
fn push(&mut self, key: &str, value: &str) {
self.push(key, value.to_string());
}
}
impl Push<Object> for TargetSpec {
fn push(&mut self, key: &str, value: Object) {
self.0.push((key.to_string(), Value::Object(value)));
}
}
impl Display for TargetSpec {
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
// We add some newlines for clarity.
formatter.write_str("{\n")?;
if let [ref rest @ .., ref last] = self.0[..] {
for (key, value) in rest {
write!(formatter, " \"{}\": {},\n", key, value)?;
}
write!(formatter, " \"{}\": {}\n", last.0, last.1)?;
}
formatter.write_str("}")
}
}
struct KernelConfig(HashMap<String, String>);
impl KernelConfig {
/// Parses `include/config/auto.conf` from `stdin`.
fn from_stdin() -> KernelConfig {
let mut result = HashMap::new();
let stdin = std::io::stdin();
let mut handle = stdin.lock();
let mut line = String::new();
loop {
line.clear();
if handle.read_line(&mut line).unwrap() == 0 {
break;
}
if line.starts_with('#') {
continue;
}
let (key, value) = line.split_once('=').expect("Missing `=` in line.");
result.insert(key.to_string(), value.trim_end_matches('\n').to_string());
}
KernelConfig(result)
}
/// Does the option exist in the configuration (any value)?
///
/// The argument must be passed without the `CONFIG_` prefix.
/// This avoids repetition and it also avoids `fixdep` making us
/// depend on it.
fn has(&self, option: &str) -> bool {
let option = "CONFIG_".to_owned() + option;
self.0.contains_key(&option)
}
}
fn main() {
let cfg = KernelConfig::from_stdin();
let mut ts = TargetSpec::new();
// `llvm-target`s are taken from `scripts/Makefile.clang`.
if cfg.has("ARM64") {
panic!("arm64 uses the builtin rustc aarch64-unknown-none target");
} else if cfg.has("RISCV") {
if cfg.has("64BIT") {
panic!("64-bit RISC-V uses the builtin rustc riscv64-unknown-none-elf target");
} else {
panic!("32-bit RISC-V is an unsupported architecture");
}
} else if cfg.has("X86_64") {
ts.push("arch", "x86_64");
ts.push(
"data-layout",
rust: upgrade to Rust 1.78.0 This is the next upgrade to the Rust toolchain, from 1.77.1 to 1.78.0 (i.e. the latest) [1]. See the upgrade policy [2] and the comments on the first upgrade in commit 3ed03f4da06e ("rust: upgrade to Rust 1.68.2"). It is much smaller than previous upgrades, since the `alloc` fork was dropped in commit 9d0441bab775 ("rust: alloc: remove our fork of the `alloc` crate") [3]. # Unstable features There have been no changes to the set of unstable features used in our own code. Therefore, the only unstable features allowed to be used outside the `kernel` crate is still `new_uninit`. However, since we finally dropped our `alloc` fork [3], all the unstable features used by `alloc` (~30 language ones, ~60 library ones) are not a concern anymore. This reduces the maintenance burden, increases the chances of new compiler versions working without changes and gets us closer to the goal of supporting several compiler versions. It also means that, ignoring non-language/library features, we are currently left with just the few language features needed to implement the kernel `Arc`, the `new_uninit` library feature, the `compiler_builtins` marker and the few `no_*` `cfg`s we pass when compiling `core`/`alloc`. Please see [4] for details. # Required changes ## LLVM's data layout Rust 1.77.0 (i.e. the previous upgrade) introduced a check for matching LLVM data layouts [5]. Then, Rust 1.78.0 upgraded LLVM's bundled major version from 17 to 18 [6], which changed the data layout in x86 [7]. Thus update the data layout in our custom target specification for x86 so that the compiler does not complain about the mismatch: error: data-layout for target `target-5559158138856098584`, `e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128`, differs from LLVM target's `x86_64-linux-gnu` default layout, `e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128` In the future, the goal is to drop the custom target specifications. Meanwhile, if we want to support other LLVM versions used in `rustc` (e.g. for LTO), we will need to add some extra logic (e.g. conditional on LLVM's version, or extracting the data layout from an existing built-in target specification). ## `unused_imports` Rust's `unused_imports` lint covers both unused and redundant imports. Now, in 1.78.0, the lint detects more cases of redundant imports [8]. Thus one of the previous patches cleaned them up. ## Clippy's `new_without_default` Clippy now suggests to implement `Default` even when `new()` is `const`, since `Default::default()` may call `const` functions even if it is not `const` itself [9]. Thus one of the previous patches implemented it. # Other changes in Rust Rust 1.78.0 introduced `feature(asm_goto)` [10] [11]. This feature was discussed in the past [12]. Rust 1.78.0 introduced `feature(const_refs_to_static)` [13] to allow referencing statics in constants and extended `feature(const_mut_refs)` to allow raw mutable pointers in constants. Together, this should cover the kernel's `VTABLE` use case. In fact, the implementation [14] in upstream Rust added a test case for it [15]. Rust 1.78.0 with debug assertions enabled (i.e. `-Cdebug-assertions=y`, kernel's `CONFIG_RUST_DEBUG_ASSERTIONS=y`) now always checks all unsafe preconditions, though without a way to opt-out for particular cases [16]. It would be ideal to have a way to selectively disable certain checks per-call site for this one (i.e. not just per check but for particular instances of a check), even if the vast majority of the checks remain in place [17]. Rust 1.78.0 also improved a couple issues we reported when giving feedback for the new `--check-cfg` feature [18] [19]. # `alloc` upgrade and reviewing As mentioned above, compiler upgrades will not update `alloc` anymore, since we dropped our `alloc` fork [3]. Link: https://github.com/rust-lang/rust/blob/stable/RELEASES.md#version-1780-2024-05-02 [1] Link: https://rust-for-linux.com/rust-version-policy [2] Link: https://lore.kernel.org/rust-for-linux/20240328013603.206764-1-wedsonaf@gmail.com/ [3] Link: https://github.com/Rust-for-Linux/linux/issues/2 [4] Link: https://github.com/rust-lang/rust/pull/120062 [5] Link: https://github.com/rust-lang/rust/pull/120055 [6] Link: https://reviews.llvm.org/D86310 [7] Link: https://github.com/rust-lang/rust/pull/117772 [8] Link: https://github.com/rust-lang/rust-clippy/pull/10903 [9] Link: https://github.com/rust-lang/rust/pull/119365 [10] Link: https://github.com/rust-lang/rust/issues/119364 [11] Link: https://lore.kernel.org/rust-for-linux/ZWipTZysC2YL7qsq@Boquns-Mac-mini.home/ [12] Link: https://github.com/rust-lang/rust/issues/119618 [13] Link: https://github.com/rust-lang/rust/pull/120932 [14] Link: https://github.com/rust-lang/rust/pull/120932/files#diff-e6fc1622c46054cd46b1d225c5386c5554564b3b0fa8a03c2dc2d8627a1079d9 [15] Link: https://github.com/rust-lang/rust/issues/120969 [16] Link: https://github.com/Rust-for-Linux/linux/issues/354 [17] Link: https://github.com/rust-lang/rust/pull/121202 [18] Link: https://github.com/rust-lang/rust/pull/121237 [19] Reviewed-by: Alice Ryhl <aliceryhl@google.com> Link: https://lore.kernel.org/r/20240401212303.537355-4-ojeda@kernel.org [ Added a few more details and links I mentioned in the list. - Miguel ] Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
2024-04-02 05:23:03 +08:00
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
);
let mut features = "-3dnow,-3dnowa,-mmx,+soft-float".to_string();
if cfg.has("MITIGATION_RETPOLINE") {
features += ",+retpoline-external-thunk";
}
ts.push("features", features);
ts.push("llvm-target", "x86_64-linux-gnu");
ts.push("target-pointer-width", "64");
} else if cfg.has("X86_32") {
// This only works on UML, as i386 otherwise needs regparm support in rustc
if !cfg.has("UML") {
panic!("32-bit x86 only works under UML");
}
ts.push("arch", "x86");
ts.push(
"data-layout",
"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",
);
let mut features = "-3dnow,-3dnowa,-mmx,+soft-float".to_string();
if cfg.has("MITIGATION_RETPOLINE") {
features += ",+retpoline-external-thunk";
}
ts.push("features", features);
ts.push("llvm-target", "i386-unknown-linux-gnu");
ts.push("target-pointer-width", "32");
} else if cfg.has("LOONGARCH") {
panic!("loongarch uses the builtin rustc loongarch64-unknown-none-softfloat target");
} else {
panic!("Unsupported architecture");
}
ts.push("emit-debug-gdb-scripts", false);
ts.push("frame-pointer", "may-omit");
ts.push(
"stack-probes",
vec![("kind".to_string(), Value::String("none".to_string()))],
);
// Everything else is LE, whether `CPU_LITTLE_ENDIAN` is declared or not
// (e.g. x86). It is also `rustc`'s default.
if cfg.has("CPU_BIG_ENDIAN") {
ts.push("target-endian", "big");
}
println!("{}", ts);
}