diff --git a/.cargo/config.toml b/.cargo/config.toml index a5751bbd83..a72a7983bf 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,13 +1,5 @@ [alias] xtask = "run --package xtask --" -[build] -# The purpose of this flag is to block crates using version_detect from "detecting" -# features that are no longer supported by the toolchain, because despite its name, -# version_detect is basically "if nightly { return true; }". This setting gets -# overridden within xtask for Hubris programs, so this only affects host tools like -# xtask. -rustflags = ["-Zallow-features=proc_macro_diagnostic,used_with_arg"] - [unstable] bindeps = true diff --git a/Cargo.lock b/Cargo.lock index abd4b26a9f..aee988927f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,13 +21,14 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy 0.8.25", ] [[package]] @@ -963,7 +964,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "syn 1.0.94", ] @@ -1620,7 +1621,6 @@ dependencies = [ "drv-lpc55-spi", "drv-lpc55-syscon-api", "drv-sp-ctrl-api", - "endoscope", "endoscope-abi", "goblin", "idol", @@ -3152,7 +3152,7 @@ checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "serde", "spin 0.9.4", "stable_deref_trait", @@ -4056,9 +4056,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "ordered-toml" @@ -4562,9 +4562,9 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver 1.0.13", ] @@ -6928,6 +6928,7 @@ dependencies = [ "regex", "ron", "rustc-demangle", + "rustc_version 0.4.1", "scroll", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 1717c7b81e..f62dc7681d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ ] default-members = [] resolver = "2" +rust-version = "1.89.0" [profile.release] codegen-units = 1 # better optimizations @@ -111,6 +112,7 @@ rangemap = { version = "1.3", default-features = false } regex = { version = "1", default-features = false, features = ["std", "perf", "unicode-perl"] } ron = { version = "0.8", default-features = false } rustc-demangle = { version = "0.1.24", default-features = false } +rustc_version = { version = "0.4.1" } scroll = { version = "0.10", default-features = false } serde = { version = "1.0.114", default-features = false, features = ["derive"] } serde-big-array = { version = "0.4", default-features = false } diff --git a/FAQ.mkdn b/FAQ.mkdn index f17938febc..3cf7006bd7 100644 --- a/FAQ.mkdn +++ b/FAQ.mkdn @@ -129,28 +129,16 @@ try to keep things working on Mac and Illumos. ### What Rust toolchain versions do you support? -Because of our reliance on nightly toolchain features, we support exactly one -version of the toolchain -- the one listed in our `rust-toolchain.toml` file at -the top of the repo. +Hubris can be built using the stable toolchain (see [Cargo.toml](Cargo.toml) for +the MSRV). This should suffice for development uses and production builds. -### Why does Hubris require the nightly Rust toolchain? - -Mostly because we need some assembly language, and we'd like to write that in -Rust functions instead of separate files, which means we need the unstable `asm` -feature. To do context switching, specifically, we need to be able to write a -function that contains _only_ assembly instructions without additional -preamble/epilogue code. For this, we need the unstable `naked_fn` feature. - -At the time of this writing, portions of `asm` are on track to stabilizing, but -unfortunately not the portions we use. - -Unfortunately, Hubris's use of the nightly toolchain more or less requires any -applications of Hubris to also use the nightly toolchain. - -Just because we're on nightly doesn't mean we can use any unstable feature -- -we're trying hard _not_ to use any additional unstable features, in the hopes of -eventually being able to use stable. +There is one feature that requires nightly: build-time task [stack overflow +checking]. Using this check requires [emit-stack-sizes], for which there is +no stable workaround or equivalent. In order to enable this check, pass the +flag `+nightly-2025-07-20` to cargo (i.e. `cargo +nightly... xtask ...`). +[stack overflow checking]: https://github.com/oxidecomputer/hubris/pull/1890 +[emit-stack-sizes]: https://doc.rust-lang.org/beta/unstable-book/compiler-flags/emit-stack-sizes.html ## Hardware support diff --git a/build/xtask/Cargo.toml b/build/xtask/Cargo.toml index c42f93afd8..993bb8dbfd 100644 --- a/build/xtask/Cargo.toml +++ b/build/xtask/Cargo.toml @@ -37,6 +37,7 @@ serde = { workspace = true } serde_json = { workspace = true } sha3 = { workspace = true } rustc-demangle = { workspace = true } +rustc_version = { workspace = true } tlvc = { workspace = true } tlvc-text = { workspace = true } toml = { workspace = true } diff --git a/build/xtask/src/config.rs b/build/xtask/src/config.rs index 63b30e51e1..6e9f09cabf 100644 --- a/build/xtask/src/config.rs +++ b/build/xtask/src/config.rs @@ -797,20 +797,6 @@ impl BuildConfig<'_> { None => PathBuf::from("cargo"), }); - let mut nightly_features = vec![]; - // nightly features that we use: - nightly_features.extend(["emit_stack_sizes", "used_with_arg"]); - // nightly features that our dependencies use: - nightly_features.extend([ - "backtrace", - "error_generic_member_access", - "proc_macro_span", - "proc_macro_span_shrink", - "provide_any", - ]); - - cmd.arg(format!("-Zallow-features={}", nightly_features.join(","))); - cmd.arg(subcommand); cmd.arg("-p").arg(&self.crate_name); for a in &self.args { diff --git a/build/xtask/src/dist.rs b/build/xtask/src/dist.rs index c32425ad6f..4a4f978f6d 100644 --- a/build/xtask/src/dist.rs +++ b/build/xtask/src/dist.rs @@ -14,10 +14,12 @@ use std::process::{Command, Stdio}; use anyhow::{anyhow, bail, Context, Result}; use atty::Stream; +use cargo_metadata::Message; use indexmap::IndexMap; use lpc55_rom_data::FLASH_PAGE_SIZE as LPC55_FLASH_PAGE_SIZE; use multimap::MultiMap; use path_slash::{PathBufExt, PathExt}; +use rustc_version::{version_meta, Channel}; use sha3::{Digest, Sha3_256}; use zerocopy::IntoBytes; @@ -373,6 +375,13 @@ pub fn package( // return value, because we're going to link them regardless of whether the // build changed. for name in cfg.toml.tasks.keys() { + // Magic for lpc55-swd to be able to include endoscope, until artifact + // dependencies are available on stable. See build_endoscope's comment for + // more. + if cfg.toml.tasks[name.as_str()].name == "drv-lpc55-swd" { + build_endoscope()?; + } + if tasks_to_build.contains(name.as_str()) { build_task(&cfg, name)?; } @@ -493,22 +502,24 @@ pub fn package( }) .collect::>()?; - // Check stack sizes and resolve task slots in our linked files - let mut possible_stack_overflow = vec![]; - for task_name in cfg.toml.tasks.keys() { - if tasks_to_build.contains(task_name.as_str()) { - if task_can_overflow(&cfg.toml, task_name, verbose)? { - possible_stack_overflow.push(task_name); - } + if version_meta().unwrap().channel == Channel::Nightly { + // Check stack sizes and resolve task slots in our linked files + let mut possible_stack_overflow = vec![]; + for task_name in cfg.toml.tasks.keys() { + if tasks_to_build.contains(task_name.as_str()) { + if task_can_overflow(&cfg.toml, task_name, verbose)? { + possible_stack_overflow.push(task_name); + } - resolve_task_slots(&cfg, task_name, image_name)?; + resolve_task_slots(&cfg, task_name, image_name)?; + } + } + if !possible_stack_overflow.is_empty() { + bail!( + "tasks may overflow: {possible_stack_overflow:?}; \ + see logs above" + ); } - } - if !possible_stack_overflow.is_empty() { - bail!( - "tasks may overflow: {possible_stack_overflow:?}; \ - see logs above" - ); } // Add an empty output section for the caboose @@ -1091,6 +1102,95 @@ struct LoadSegment { data: Vec, } +/// Build the endoscope binary here because it can't be built by stable cargo yet +/// +/// This function invokes cargo directly in order to build the endoscope crate's binary target. +/// Endoscope is a dependency of the lpc55-swd driver, but until artifact dependencies are +/// stabilized, cargo cannot be used to build it. +/// +/// For more see https://github.com/rust-lang/cargo/issues/9096 +/// +/// Once it's built and the environment variable is set, lpc55-swd's `build.rs` file is able to +/// find, and include, the binary's bytes in its output. +fn build_endoscope() -> Result<()> { + let mut cmd = Command::new(std::env::var("CARGO")?); + cmd.args([ + "build", + "--release", + "-p", + "endoscope", + "--target", + "thumbv7em-none-eabihf", + "--features", + "soc_stm32h753", + "--message-format", + "json", + ]); + cmd.env("CARGO_TARGET_DIR", "target"); // share target dir (optional) + let output = cmd.stdout(Stdio::piped()).spawn()?.wait_with_output()?; + + // inspect cargo output to determine whether the build failed + let did_cargo_succeed = Message::parse_stream(output.stdout.as_slice()) + .filter_map(|msg| { + if let Ok(Message::BuildFinished(bf)) = msg { + Some(bf.success) + } else { + None + } + }) + .all(|s| s); + + // if cargo did not succeed or exited with a non-success exit code, grab the rendered output + // from each compiler message and try to build something approximating normal compiler output. + if !did_cargo_succeed || !output.status.success() { + return Err(anyhow!( + "building endoscope did not succeed: \n{}", + Message::parse_stream(output.stdout.as_slice()) + .filter_map(|msg| { + if let Ok(Message::CompilerMessage(msg)) = msg { + Some( + String::from_utf8( + msg.message + .rendered + .unwrap_or("no output rendered".to_string()) + .into(), + ) + .unwrap_or("output is not valid utf-8".to_string()), + ) + } else { + None + } + }) + .collect::>() + .join("\n") + )); + } + + // Parse path to the produced ELF + let mut path = None; + for msg in Message::parse_stream(output.stdout.as_slice()).flatten() { + if let Message::CompilerArtifact(art) = msg { + if art.target.kind.iter().any(|k| k == "bin") + && art.target.name == "endoscope" + { + path = art.executable.clone(); // Some(PathBuf) + } + } + } + match path { + Some(path) => { + // setting this env var lets the lpc55-swd `build.rs` file know where to find the built + // binary. this would otherwise be done by cargo as part of artifact dependency + // support. + std::env::set_var("CARGO_BIN_FILE_ENDOSCOPE", &path); + Ok(()) + } + None => Err(anyhow!( + "endoscope was built but the binary could not be located" + )), + } +} + /// Builds a specific task fn build_task(cfg: &PackageConfig, name: &str) -> Result<()> { // Use relocatable linker script for this build @@ -2004,20 +2104,38 @@ fn build( ); output }); - cmd.env( - "RUSTFLAGS", - format!( - "-C link-arg=-z -C link-arg=common-page-size=0x20 \ - -C link-arg=-z -C link-arg=max-page-size=0x20 \ - -C llvm-args=--enable-machine-outliner=never \ - -Z emit-stack-sizes \ - -C overflow-checks=y \ - -C metadata={} \ - {} - ", - cfg.link_script_hash, remap_path_prefix, - ), - ); + + if version_meta().unwrap().channel == Channel::Nightly { + cmd.env( + "RUSTFLAGS", + format!( + "-C link-arg=-z -C link-arg=common-page-size=0x20 \ + -C link-arg=-z -C link-arg=max-page-size=0x20 \ + -C llvm-args=--enable-machine-outliner=never \ + -Z emit-stack-sizes \ + -C overflow-checks=y \ + -C metadata={} \ + {} + ", + cfg.link_script_hash, remap_path_prefix, + ), + ); + } else { + cmd.env( + "RUSTFLAGS", + format!( + "-C link-arg=-z -C link-arg=common-page-size=0x20 \ + -C link-arg=-z -C link-arg=max-page-size=0x20 \ + -C llvm-args=--enable-machine-outliner=never \ + -C overflow-checks=y \ + -C metadata={} \ + {} + ", + cfg.link_script_hash, remap_path_prefix, + ), + ); + } + cmd.arg("--"); // We use attributes to conditionally import based on feature flags; diff --git a/drv/lpc55-swd/Cargo.toml b/drv/lpc55-swd/Cargo.toml index 43d851f2a3..3ac22c02a2 100644 --- a/drv/lpc55-swd/Cargo.toml +++ b/drv/lpc55-swd/Cargo.toml @@ -30,7 +30,11 @@ build-lpc55pins = { path = "../../build/lpc55pins" } build-util = { path = "../../build/util" } call_rustfmt = { path = "../../build/call_rustfmt" } endoscope-abi = { path = "../../lib/endoscope-abi" } -endoscope = { path = "../../lib/endoscope", artifact="bin:endoscope", target = "thumbv7em-none-eabihf", features = ["soc_stm32h753"]} +# "artifact dependencies" (i.e. `artifact="bin:endoscope"` et al.) are not supported in stable. As +# such, building endoscope is done directly by the `dist` xtask when building this crate. See the +# `build_endoscope` function in `build/xtask/src/dist.rs`. +# For more, see https://github.com/rust-lang/cargo/issues/9096 +# endoscope = { path = "../../lib/endoscope", artifact="bin:endoscope", target = "thumbv7em-none-eabihf", features = ["soc_stm32h753"]} goblin = { workspace = true } idol = { workspace = true } quote = { workspace = true } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index be521e9c13..23a4041619 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2025-07-20" +channel = "stable" targets = [ "thumbv6m-none-eabi", "thumbv7em-none-eabihf", "thumbv8m.main-none-eabihf" ] profile = "minimal" components = [ "rustfmt" ] diff --git a/sys/userlib/src/lib.rs b/sys/userlib/src/lib.rs index ba9a8862c9..59d31452ce 100644 --- a/sys/userlib/src/lib.rs +++ b/sys/userlib/src/lib.rs @@ -17,7 +17,7 @@ //! hard time moving values into registers r6, r7, and r11. Because (for better //! or worse) the syscall ABI uses these registers, we have to take extra steps. //! -//! The `stub` function contains the actual `asm!` call sequence. It is `naked`, +//! The `stub` function contains the actual `naked_asm!` call sequence. It is `naked`, //! meaning the compiler will *not* attempt to do any framepointer/basepointer //! nonsense, and we can thus reason about the assignment and availability of //! all registers. @@ -1066,7 +1066,8 @@ unsafe extern "C" fn sys_panic_stub(_msg: *const u8, _len: usize) -> ! { @ To the kernel! svc #0 - @ noreturn generates a udf to trap us if it returns. + @ if the handler ever returns, trap + udf 0xde ", sysnum = const Sysnum::Panic as u32, ) @@ -1085,7 +1086,8 @@ unsafe extern "C" fn sys_panic_stub(_msg: *const u8, _len: usize) -> ! { @ To the kernel! svc #0 - @ noreturn generates a udf to trap us if it returns. + @ if the handler ever returns, trap + udf 0xde ", sysnum = const Sysnum::Panic as u32, ) @@ -1268,9 +1270,9 @@ pub unsafe extern "C" fn _start() -> ! { @ a sym operand because it's a Rust func and may be mangled. bl {main} - @ The noreturn option below will automatically generate an - @ undefined instruction trap past this point, should main + @ Add an undefined instruction to trap past this point, should main @ return. + udf 0xde ", main = sym main, ) @@ -1325,9 +1327,9 @@ pub unsafe extern "C" fn _start() -> ! { @ a sym operand because it's a Rust func and may be mangled. bl {main} - @ The noreturn option below will automatically generate an - @ undefined instruction trap past this point, should main + @ Add an undefined instruction to trap past this point, should main @ return. + udf 0xde ", main = sym main, ) diff --git a/task/host-sp-comms/src/main.rs b/task/host-sp-comms/src/main.rs index 3835dfda09..28484f57e9 100644 --- a/task/host-sp-comms/src/main.rs +++ b/task/host-sp-comms/src/main.rs @@ -1349,7 +1349,6 @@ impl ServerImpl { // space we have for our response, then statically // guarantee we have sufficient space in `buf` for // longest possible DTRACE_CONF blob. - #[allow(dead_code)] // suppress warning in nightly const SP_TO_HOST_FILL_DATA_LEN: usize = MIN_SP_TO_HOST_FILL_DATA_LEN + SpToHost::MAX_SIZE diff --git a/test/test-suite/src/main.rs b/test/test-suite/src/main.rs index 6cf0eec489..316f37f3a5 100644 --- a/test/test-suite/src/main.rs +++ b/test/test-suite/src/main.rs @@ -18,7 +18,6 @@ //! //! The Idol server, `test-idol-server`, tests Idol-mediated IPC. It must be //! included in the image with the name `idol`, but its ID is immaterial. -#![feature(used_with_arg)] #![no_std] #![no_main] #![forbid(clippy::wildcard_imports)] @@ -49,12 +48,12 @@ const BAD_ADDRESS: u32 = 0x0; /// Helper macro for building a list of functions with their names. /// We use the humility debug processing to get the name of each -/// test case and the total number of tests. The #[used(linker)] -/// is to ensure that this actually gets emitted as a symbol. +/// test case and the total number of tests. The #[used] is to ensure +/// that this actually gets emitted as a symbol. macro_rules! test_cases { ($($(#[$attr:meta])* $name:path,)*) => { #[no_mangle] - #[used(linker)] + #[used] static TESTS: &[(&str, &(dyn Fn() + Send + Sync))] = &[ $( $(#[$attr])*