From 3bcb82d7dde9c2cfda7d0532ccb6eb710ace729e Mon Sep 17 00:00:00 2001 From: Sagar Dhawan Date: Thu, 5 Mar 2026 19:12:44 -0600 Subject: [PATCH] fix: stop forwarding host RUSTFLAGS into RISC-V cross-compilation RUSTFLAGS and CARGO_ENCODED_RUSTFLAGS target the host compiler. Forwarding them into riscv64imac cross-compilation leaks host-specific flags like -C target-cpu=native and -C embed-bitcode=yes, which LLVM rejects with "RV64 target requires an RV64 CPU". Replace the RUSTFLAGS injection in build-syscall-cycles.sh with a new --no-machine-outliner flag on BuildArgs. The machine outliner disable is an internal correctness invariant of the cycle-counting tool, not a user-configurable knob, so it belongs in the build interface rather than the caller's environment. Also add ZEROOS_GUEST_RUSTFLAGS as an explicit opt-in escape hatch for callers that genuinely need to inject flags into the guest build. The name makes the target unambiguous and prevents accidental host flag leakage. --- build-syscall-cycles.sh | 5 ++- crates/zeroos-build/src/cmds/build.rs | 44 ++++++++++++++++++++------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/build-syscall-cycles.sh b/build-syscall-cycles.sh index fa3da82..c002039 100755 --- a/build-syscall-cycles.sh +++ b/build-syscall-cycles.sh @@ -14,13 +14,12 @@ cd "${ROOT}" echo "Building syscall-cycles example in std mode ..." if [[ "${PROFILE}" = "release" ]]; then # Keep release profile tuning explicit (avoid per-crate [profile.release] warnings). - # Disable machine outliner to avoid OUTLINED_FUNCTION_* symbols interfering with cycle counts. + # --no-machine-outliner prevents OUTLINED_FUNCTION_* symbols from corrupting cycle counts. CARGO_PROFILE_RELEASE_DEBUG=2 \ CARGO_PROFILE_RELEASE_STRIP=none \ CARGO_PROFILE_RELEASE_LTO=true \ CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 \ - RUSTFLAGS="${RUSTFLAGS:-} -Cllvm-args=-enable-machine-outliner=never" \ - cargo spike build -p syscall-cycles --target "${TARGET_TRIPLE}" --mode std -- --quiet --features=std --profile "${PROFILE}" + cargo spike build -p syscall-cycles --target "${TARGET_TRIPLE}" --mode std --no-machine-outliner -- --quiet --features=std --profile "${PROFILE}" else cargo spike build -p syscall-cycles --target "${TARGET_TRIPLE}" --mode std -- --quiet --features=std --profile "${PROFILE}" fi diff --git a/crates/zeroos-build/src/cmds/build.rs b/crates/zeroos-build/src/cmds/build.rs index b20b8e1..7c5a902 100644 --- a/crates/zeroos-build/src/cmds/build.rs +++ b/crates/zeroos-build/src/cmds/build.rs @@ -64,6 +64,15 @@ pub struct BuildArgs { #[arg(long, env = "RISCV_GCC_PATH")] pub gcc_lib_path: Option, + /// Disable the LLVM machine outliner for this guest build. + /// + /// The machine outliner extracts repeated instruction sequences into helper functions + /// named `OUTLINED_FUNCTION_*`. This is usually a win for code size, but it corrupts + /// cycle-count measurements by splitting hot paths across synthetic symbols. Use this + /// flag in any build where symbol-level cycle attribution must be exact. + #[arg(long)] + pub no_machine_outliner: bool, + /// Arguments after `--` are forwarded to the underlying `cargo build` invocation. /// /// Example: @@ -197,16 +206,13 @@ pub fn build_binary_with_rustflags( debug!(" link_arg[{}]: {}", i, arg); } - let mut rustflags_parts: Vec = std::env::var("CARGO_ENCODED_RUSTFLAGS") - .ok() - .map(|s| s.split('\x1f').map(|s| s.to_string()).collect()) - .unwrap_or_default(); - - if let Ok(rustflags) = std::env::var("RUSTFLAGS") { - for flag in rustflags.split_whitespace() { - rustflags_parts.push(flag.to_string()); - } - } + // Start from an empty set — only flags we explicitly construct belong in a RISC-V + // cross-compilation. Neither RUSTFLAGS nor CARGO_ENCODED_RUSTFLAGS are forwarded here + // because those variables target the host build and can contain host-specific flags + // (e.g. -C target-cpu=native, -C embed-bitcode=yes) that LLVM rejects for riscv64imac. + // Callers that need to inject guest-specific flags should use --no-machine-outliner, + // additional_rustflags, or the ZEROOS_GUEST_RUSTFLAGS env var (see below). + let mut rustflags_parts: Vec = Vec::new(); // Set cfg flag for conditional compilation based on backtrace mode match backtrace_mode { @@ -237,13 +243,29 @@ pub fn build_binary_with_rustflags( rustflags_parts.push("-Zmacro-backtrace".to_string()); } - // Add platform-specific rustflags + // Add platform-specific rustflags from the caller. if let Some(flags) = additional_rustflags { for flag in flags { rustflags_parts.push(flag.to_string()); } } + // Disable the LLVM machine outliner when requested. The outliner extracts repeated + // instruction sequences into OUTLINED_FUNCTION_* stubs, which corrupts cycle-count + // measurements by splitting hot paths across synthetic symbols. + if args.no_machine_outliner { + rustflags_parts.push("-Cllvm-args=-enable-machine-outliner=never".to_string()); + } + + // ZEROOS_GUEST_RUSTFLAGS: explicit opt-in escape hatch for guest-only flags. + // This is intentionally separate from RUSTFLAGS (which targets the host) so callers + // can't accidentally leak host-CPU-specific flags into the RISC-V cross-compilation. + if let Ok(guest_flags) = std::env::var("ZEROOS_GUEST_RUSTFLAGS") { + for flag in guest_flags.split_ascii_whitespace() { + rustflags_parts.push(flag.to_string()); + } + } + let encoded_rustflags = rustflags_parts.join("\x1f"); debug!("CARGO_ENCODED_RUSTFLAGS: {:?}", encoded_rustflags);