Skip to content

Commit

Permalink
scripts: add generate_rust_target.rs
Browse files Browse the repository at this point in the history
This script takes care of generating the custom target specification
file for `rustc`, based on the kernel configuration.

It also serves as an example of a Rust host program.

A dummy architecture is kept in this patch so that a later patch
adds x86 support on top with as few changes as possible.

Reviewed-by: Kees Cook <keescook@chromium.org>
Co-developed-by: Alex Gaynor <alex.gaynor@gmail.com>
Signed-off-by: Alex Gaynor <alex.gaynor@gmail.com>
Co-developed-by: Wedson Almeida Filho <wedsonaf@google.com>
Signed-off-by: Wedson Almeida Filho <wedsonaf@google.com>
Co-developed-by: David Gow <davidgow@google.com>
Signed-off-by: David Gow <davidgow@google.com>
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
  • Loading branch information
ojeda committed Sep 28, 2022
1 parent 8c4555c commit 9a8ff24
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 0 deletions.
1 change: 1 addition & 0 deletions scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
/asn1_compiler
/bin2c
/generate_rust_target
/insert-sys-cert
/kallsyms
/module.lds
Expand Down
171 changes: 171 additions & 0 deletions scripts/generate_rust_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// 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("DUMMY_ARCH") {
ts.push("arch", "dummy_arch");
} 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);
}

0 comments on commit 9a8ff24

Please sign in to comment.