Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ rustflags = [
# which wraps it in an ISO and boots it under QEMU.
runner = "target/release/xtask test-runner"

# Userspace target: vibix std programs cross-compiled with -Z build-std.
# The target spec JSON lives at the workspace root; rustflags here apply
# when --target=x86_64-unknown-vibix.json is passed explicitly by xtask.
[target.x86_64-unknown-vibix]
rustflags = [
"-C", "relocation-model=static",
"-C", "no-redzone=yes",
"-C", "force-frame-pointers=yes",
]

# `build-std` is intentionally NOT in [unstable] here — it would apply
# to host `cargo test --lib` too and clash with the sysroot's std.
# xtask passes `-Z build-std=...` explicitly whenever it invokes cargo
Expand Down
27 changes: 27 additions & 0 deletions x86_64-unknown-vibix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"llvm-target": "x86_64-unknown-none-elf",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "vibix",
"env": "",
"vendor": "unknown",
"linker-flavor": "gnu-lld",
"linker": "rust-lld",
"pre-link-args": {
"gnu-lld": ["-nostdlib", "-static"]
},
"panic-strategy": "abort",
"disable-redzone": true,
"features": "+sse,+sse2",
"has-thread-local": true,
"tls-model": "initial-exec",
"position-independent-executables": false,
"static-position-independent-executables": false,
"relocation-model": "static",
"executables": true,
"max-atomic-width": 64,
"crt-objects-fallback": "false"
}
1 change: 1 addition & 0 deletions xtask/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ path = "src/main.rs"
object = { version = "0.36", default-features = false, features = ["read", "std"] }
rustc-demangle = "0.1"
gimli = { version = "0.31", default-features = false, features = ["read-all"] }
serde_json = "1"
105 changes: 104 additions & 1 deletion xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ const KERNEL_BUILD_STD_ARGS: &[&str] = &[
"build-std-features=compiler-builtins-mem",
];

/// Custom target spec for vibix userspace (std programs cross-compiled
/// with `-Z build-std`). The JSON file lives at the workspace root.
const VIBIX_USERSPACE_TARGET: &str = "x86_64-unknown-vibix.json";

// QEMU process exit codes produced by `isa-debug-exit` writing our
// QemuExitCode values. See kernel/src/test_harness.rs.
const QEMU_EXIT_SUCCESS: i32 = 65; // (0x20 << 1) | 1
Expand Down Expand Up @@ -393,6 +397,9 @@ fn main() -> R<()> {
}
}
"clean" => clean()?,
"validate-target" => {
validate_vibix_target()?;
}
"bench" => {
// `xtask bench <target>`. Currently only `page-cache` is
// wired (#743 — RFC 0007 host-side cold-mmap fault-latency
Expand Down Expand Up @@ -427,7 +434,7 @@ fn main() -> R<()> {
other => {
eprintln!("unknown subcommand: {other}");
eprintln!(
"usage: cargo xtask [build|initrd|ext2-image|iso|run|test|test-unit|test-integration|smoke|pjdfstest|repro-fork|repro-fork-build|shell-pipeline|lint|isr-audit|nm-check|bench|fuzz|clean] [--release] [--fault-test] [--panic-test] [--bench] [--fork-trace] [--shard=I/N (test-integration only)]"
"usage: cargo xtask [build|initrd|ext2-image|iso|run|test|test-unit|test-integration|smoke|pjdfstest|repro-fork|repro-fork-build|shell-pipeline|lint|isr-audit|nm-check|validate-target|bench|fuzz|clean] [--release] [--fault-test] [--panic-test] [--bench] [--fork-trace] [--shard=I/N (test-integration only)]"
);
std::process::exit(2);
}
Expand Down Expand Up @@ -3416,3 +3423,99 @@ repro: starting fork loop cycles=500 hb=50
}
}
}

// ─── validate-target ───────────────────────────────────────────────────────

/// Validate that the `x86_64-unknown-vibix.json` target spec is well-formed
/// and contains the required fields for vibix userspace cross-compilation.
fn validate_vibix_target() -> R<()> {
let spec_path = workspace_root().join(VIBIX_USERSPACE_TARGET);
if !spec_path.exists() {
return Err(format!(
"target spec not found at {}",
spec_path.display()
)
.into());
}

let contents = fs::read_to_string(&spec_path)?;
let spec: serde_json::Value = serde_json::from_str(&contents).map_err(|e| {
format!(
"target spec {} is not valid JSON: {e}",
spec_path.display()
)
})?;

let obj = spec
.as_object()
.ok_or("target spec root is not a JSON object")?;

// Required fields and their expected values (where a specific value is
// mandated by the RFC). `None` means "must be present, any value ok".
let required: &[(&str, Option<&str>)] = &[
("llvm-target", Some("x86_64-unknown-none-elf")),
("arch", Some("x86_64")),
("os", Some("vibix")),
("linker-flavor", Some("gnu-lld")),
("linker", Some("rust-lld")),
("panic-strategy", Some("abort")),
("relocation-model", Some("static")),
("tls-model", Some("initial-exec")),
("features", Some("+sse,+sse2")),
("data-layout", None),
("target-endian", Some("little")),
("target-pointer-width", Some("64")),
];

let mut errors = Vec::new();
for &(key, expected_val) in required {
match obj.get(key) {
None => errors.push(format!("missing required field: \"{key}\"")),
Some(val) => {
if let Some(expected) = expected_val {
let actual = val.as_str().unwrap_or("");
if actual != expected {
errors.push(format!(
"field \"{key}\": expected \"{expected}\", got \"{actual}\""
));
}
}
}
}
}

// Boolean fields that must be true.
for key in ["has-thread-local", "disable-redzone"] {
match obj.get(key) {
None => errors.push(format!("missing required field: \"{key}\"")),
Some(val) => {
if val.as_bool() != Some(true) {
errors.push(format!("field \"{key}\" must be true"));
}
}
}
}

// Boolean fields that must be false.
for key in ["position-independent-executables"] {
match obj.get(key) {
None => errors.push(format!("missing required field: \"{key}\"")),
Some(val) => {
if val.as_bool() != Some(false) {
errors.push(format!("field \"{key}\" must be false"));
}
}
}
}

if errors.is_empty() {
println!("✓ target spec {} is well-formed", spec_path.display());
Ok(())
} else {
Err(format!(
"target spec validation failed:\n • {}",
errors.join("\n • ")
)
.into())
}
}
Loading