From 514b7d32b245da4dcc63a19fa7188cdad72b37cf Mon Sep 17 00:00:00 2001 From: Sam Sung <51686767+samwhosung@users.noreply.github.com> Date: Sun, 1 Nov 2026 08:17:05 +0700 Subject: [PATCH] Every launcher that calls the build stamp gets the 8 MB main stack (#2940) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows main-thread reserve was set only in `crates/benilla/build.rs`. So `benilla-worldview`, and every launcher built on benilla by the recipe in `extended_launcher.rs`, linked with the linker's default: 1 MB on MSVC, 2 MB on mingw. That is less than the login screen needs. `benilla_buildstamp::emit()`, which every launcher's build script already calls, now asks for 8 MB through `rustc-link-arg`: `/STACK` on MSVC, `--stack` on mingw. Unlike `rustc-link-arg-bins`, cargo accepts it from a package with no binary, so a library that calls `emit()` still builds. The recipe and CONTRIBUTING's "Building on top" say so. Test, mingw cross-build: `SizeOfStackReserve` is 0x800000 for `benilla.exe` (unchanged) and `benilla-worldview.exe` (was 0x200000). A library-only crate calling `emit()` checks clean on MSVC and builds on mingw. Fixes #2934 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 --- .../benilla-app/examples/extended_launcher.rs | 7 ++- crates/benilla-buildstamp/src/lib.rs | 63 +++++++++++++++++-- crates/benilla-worldview/build.rs | 3 +- crates/benilla/build.rs | 19 +----- docs/CONTRIBUTING.md | 9 +-- 5 files changed, 70 insertions(+), 31 deletions(-) diff --git a/crates/benilla-app/examples/extended_launcher.rs b/crates/benilla-app/examples/extended_launcher.rs index 15214e7e1..3873839e9 100644 --- a/crates/benilla-app/examples/extended_launcher.rs +++ b/crates/benilla-app/examples/extended_launcher.rs @@ -10,8 +10,9 @@ //! `AddOns/` folder in [`benilla_app::config_dir`], where an addon the crate ships is written. //! //! A crate of its own copies the `benilla` launcher (`crates/benilla`): a build script that stamps -//! its commit, a `main` like this one that reads the stamp back as `crates/benilla/src/main.rs` -//! does, and a manifest that takes benilla as the workspace does: +//! its commit and gives its binary the 8 MB main-thread stack the app needs on Windows, a `main` +//! like this one that reads the stamp back as `crates/benilla/src/main.rs` does, and a manifest +//! that takes benilla as the workspace does: //! //! ```toml //! [features] @@ -45,7 +46,7 @@ //! ``` //! //! ```text -//! // build.rs +//! // build.rs: the stamp, and on Windows the main-thread stack of every binary in the package //! fn main() { //! benilla_buildstamp::emit(); //! } diff --git a/crates/benilla-buildstamp/src/lib.rs b/crates/benilla-buildstamp/src/lib.rs index 9ce0f46cd..6b542789c 100644 --- a/crates/benilla-buildstamp/src/lib.rs +++ b/crates/benilla-buildstamp/src/lib.rs @@ -1,14 +1,20 @@ //! The build stamp: the `build.rs` body the `benilla` and `benilla-worldview` launcher shims -//! share. It stays a build-dependency of the shims, never of `benilla-app`: cargo rebuilds the -//! package that owns a `rerun-if-changed` path whenever that path's mtime moves, and these paths -//! move on every commit, rebase and checkout. +//! share, and every launcher a crate on top builds the same way. It stays a build-dependency of +//! the shims, never of `benilla-app`: cargo rebuilds the package that owns a `rerun-if-changed` +//! path whenever that path's mtime moves, and these paths move on every commit, rebase and +//! checkout. use std::path::Path; use std::process::Command; -/// Stamp the commit this binary was built from into the calling package as six `rustc-env` -/// vars, which its `main.rs` reads back with `env!` into a `BuildId`, beside its own -/// `CARGO_PKG_VERSION`: +/// The main thread's stack on Windows, in bytes: what Linux and macOS give it. +const MAIN_THREAD_STACK: u32 = 8 << 20; + +/// Stamp the commit this binary was built from into the calling package, and give its executables +/// the main-thread stack the app needs on Windows. +/// +/// The stamp is six `rustc-env` vars, which its `main.rs` reads back with `env!` into a +/// `BuildId`, beside its own `CARGO_PKG_VERSION`: /// /// - `BENILLA_GIT_SHA`: the full sha. /// - `BENILLA_GIT_SHORT`: git's own abbreviation of it. @@ -94,6 +100,34 @@ pub fn emit() { String::new() }; println!("cargo::rustc-env=BENILLA_PROJECT_DIR={project}"); + + main_thread_stack(); +} + +/// The app runs on the main thread, where the windowing event loop must live, and needs more than +/// the 1 MB the MSVC linker reserves for it by default (2 MB on mingw): the login screen overflows +/// it. So the package's Windows executables reserve [`MAIN_THREAD_STACK`] through +/// `rustc-link-arg`, which cargo passes to every target it links and, unlike +/// `rustc-link-arg-bins`, takes from a package that has no binary. +fn main_thread_stack() { + let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default(); + if let Some(arg) = stack_link_arg(&os, &env) { + println!("cargo::rustc-link-arg={arg}"); + } +} + +/// The linker's spelling of the main-thread reserve for a target: `/STACK` on MSVC, `--stack` +/// through the compiler driver on mingw; none off Windows, where the system gives 8 MB already. +fn stack_link_arg(target_os: &str, target_env: &str) -> Option { + if target_os != "windows" { + return None; + } + match target_env { + "msvc" => Some(format!("/STACK:{MAIN_THREAD_STACK}")), + "gnu" => Some(format!("-Wl,--stack,{MAIN_THREAD_STACK}")), + _ => None, + } } /// The workspace root of the package at `manifest_dir`: the nearest folder up from it holding @@ -135,6 +169,23 @@ mod tests { std::fs::remove_dir_all(&bare).ok(); } + /// A Windows binary reserves 8 MB for its main thread in the linker's own spelling; no other + /// target is asked to. + #[test] + fn a_windows_binary_reserves_the_main_threads_8_mb() { + assert_eq!( + stack_link_arg("windows", "msvc").as_deref(), + Some("/STACK:8388608") + ); + assert_eq!( + stack_link_arg("windows", "gnu").as_deref(), + Some("-Wl,--stack,8388608") + ); + for (os, env) in [("macos", ""), ("linux", "gnu"), ("windows", "")] { + assert_eq!(stack_link_arg(os, env), None, "{os} {env}"); + } + } + /// benilla's own launchers stamp the checkout, the folder a dev build always used. #[test] fn a_launcher_in_this_checkout_stamps_the_checkout() { diff --git a/crates/benilla-worldview/build.rs b/crates/benilla-worldview/build.rs index 89c95e58b..1fd519923 100644 --- a/crates/benilla-worldview/build.rs +++ b/crates/benilla-worldview/build.rs @@ -1,4 +1,5 @@ -//! Stamps the commit this binary was built from. +//! Stamps the commit this binary was built from, and gives the main thread the stack the app needs +//! on Windows. fn main() { benilla_buildstamp::emit(); diff --git a/crates/benilla/build.rs b/crates/benilla/build.rs index 7298eca3c..1fd519923 100644 --- a/crates/benilla/build.rs +++ b/crates/benilla/build.rs @@ -1,21 +1,6 @@ -//! Stamps the commit this binary was built from, and gives the main thread the stack the app needs. +//! Stamps the commit this binary was built from, and gives the main thread the stack the app needs +//! on Windows. fn main() { benilla_buildstamp::emit(); - main_thread_stack(); -} - -/// The app runs on the main thread, where the windowing event loop must live, and needs more than -/// the 1 MB the MSVC linker reserves for it by default (2 MB on mingw): the login screen overflows -/// it. Linux and macOS give the main thread 8 MB, so Windows reserves the same. -fn main_thread_stack() { - const STACK: u32 = 8 << 20; - if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { - return; - } - match std::env::var("CARGO_CFG_TARGET_ENV").as_deref() { - Ok("msvc") => println!("cargo:rustc-link-arg-bins=/STACK:{STACK}"), - Ok("gnu") => println!("cargo:rustc-link-arg-bins=-Wl,--stack,{STACK}"), - _ => {} - } } diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ccb323102..174501937 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -40,10 +40,11 @@ taken. A feature 1.12.1 does not have lives in a crate of its own that adds its Bevy plugins through `benilla_app::run_with`, as `crates/benilla-app/examples/extended_launcher.rs` shows, or in a fork. A client started through `run_with` says `extended` in its build line, so its reports read -apart from stock benilla's. The crate keeps its own settings, never rows in benilla's CVar table, -which every addon reads, and its files go under `benilla_app::config_dir()`, its own -`benilla-config/`: an addon it ships is written into that folder's `AddOns/` and loads as any -addon does. +apart from stock benilla's. Its build script calls `benilla_buildstamp::emit()`, which stamps the +commit that line names and gives its binary the 8 MB main-thread stack the app needs on Windows. +The crate keeps its own settings, never rows in benilla's CVar table, which every addon reads, +and its files go under `benilla_app::config_dir()`, its own `benilla-config/`: an addon it ships +is written into that folder's `AddOns/` and loads as any addon does. Past `run_with` and that folder, benilla opens a piece of itself to such a crate only when its own 1.12.1 work creates that piece, never for the crate alone, and promises no stable API: a crate