From e96e15fa6cf7c2be886295215ecb5317040bdef2 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Fri, 17 Jan 2025 23:23:56 +0200 Subject: [PATCH 1/2] Add `-C hint-mostly-unused` to tell rustc that most of a crate will go unused This hint allows the compiler to optimize its operation based on this assumption, in order to compile faster. This is a hint, and does not guarantee any particular behavior. This option can substantially speed up compilation if applied to a large dependency where the majority of the dependency does not get used. This flag may slow down compilation in other cases. Currently, this option makes the compiler defer as much code generation as possible from functions in the crate, until later crates invoke those functions. Functions that never get invoked will never have code generated for them. For instance, if a crate provides thousands of functions, but only a few of them will get called, this flag will result in the compiler only doing code generation for the called functions. (This uses the same mechanisms as cross-crate inlining of functions.) This does not affect `extern` functions, or functions marked as `#[inline(never)]`. Some performance numbers, based on a crate with many dependencies having just *one* large dependency set to `-C hint-mostly-unused` (using Cargo's `profile-rustflags` option): A release build went from 4m32s to 2m06s. A non-release build went from 2m13s to 1m24s. --- compiler/rustc_interface/src/tests.rs | 1 + .../src/cross_crate_inline.rs | 6 ++++++ compiler/rustc_session/src/options.rs | 2 ++ src/doc/rustc/src/codegen-options/index.md | 19 +++++++++++++++++++ 4 files changed, 28 insertions(+) diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 07c4b8987216c..d5a037dfc31e6 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -615,6 +615,7 @@ fn test_codegen_options_tracking_hash() { tracked!(embed_bitcode, false); tracked!(force_frame_pointers, FramePointer::Always); tracked!(force_unwind_tables, Some(true)); + tracked!(hint_mostly_unused, true); tracked!(instrument_coverage, InstrumentCoverage::Yes); tracked!(link_dead_code, Some(true)); tracked!(linker_plugin_lto, LinkerPluginLto::LinkerPluginAuto); diff --git a/compiler/rustc_mir_transform/src/cross_crate_inline.rs b/compiler/rustc_mir_transform/src/cross_crate_inline.rs index 8fce856687cb8..8f79b03dded8f 100644 --- a/compiler/rustc_mir_transform/src/cross_crate_inline.rs +++ b/compiler/rustc_mir_transform/src/cross_crate_inline.rs @@ -50,6 +50,12 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { _ => {} } + // If the crate is likely to be mostly unused, use cross-crate inlining to defer codegen until + // the function is referenced, in order to skip codegen for unused functions. + if tcx.sess.opts.cg.hint_mostly_unused { + return true; + } + let sig = tcx.fn_sig(def_id).instantiate_identity(); for ty in sig.inputs().skip_binder().iter().chain(std::iter::once(&sig.output().skip_binder())) { diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 63aaa3abc8e56..4120f246e42b8 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1620,6 +1620,8 @@ options! { #[rustc_lint_opt_deny_field_access("use `Session::must_emit_unwind_tables` instead of this field")] force_unwind_tables: Option = (None, parse_opt_bool, [TRACKED], "force use of unwind tables"), + hint_mostly_unused: bool = (false, parse_bool, [TRACKED], + "hint that most of this crate will go unused, to minimize work for uncalled functions"), incremental: Option = (None, parse_opt_string, [UNTRACKED], "enable incremental compilation"), #[rustc_lint_opt_deny_field_access("documented to do nothing")] diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md index f45217c69ff0f..0cc39a271da76 100644 --- a/src/doc/rustc/src/codegen-options/index.md +++ b/src/doc/rustc/src/codegen-options/index.md @@ -172,6 +172,25 @@ values: The default if not specified depends on the target. +## hint-mostly-unused + +This flag hints to the compiler that most of the crate will probably go unused. +The compiler can optimize its operation based on this assumption, in order to +compile faster. This is a hint, and does not guarantee any particular behavior. + +This option can substantially speed up compilation if applied to a large +dependency where the majority of the dependency does not get used. This flag +may slow down compilation in other cases. + +Currently, this option makes the compiler defer as much code generation as +possible from functions in the crate, until later crates invoke those +functions. Functions that never get invoked will never have code generated for +them. For instance, if a crate provides thousands of functions, but only a few +of them will get called, this flag will result in the compiler only doing code +generation for the called functions. (This uses the same mechanisms as +cross-crate inlining of functions.) This does not affect `extern` functions, or +functions marked as `#[inline(never)]`. + ## incremental This flag allows you to enable incremental compilation, which allows `rustc` From d3f3a68c4726227a1397345a854f2038ca56a5e5 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sat, 18 Jan 2025 08:01:34 +0200 Subject: [PATCH 2/2] Make `-C hint-mostly-unused` require `-Zunstable-options` for now --- compiler/rustc_session/src/config.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 21afb7df7cb3a..5eba309039ec4 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2441,6 +2441,12 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M } } + if cg.hint_mostly_unused && !unstable_opts.unstable_options { + early_dcx.early_fatal( + "`-C hint-mostly-unused` requires `-Zunstable-options` and a nightly compiler", + ) + } + if !nightly_options::is_unstable_enabled(matches) && cg.force_frame_pointers == FramePointer::NonLeaf {