Skip to content

Commit 4710fdd

Browse files
committed
Introduce OptimizationObjective and run the MIR inliner for size
1 parent 3e014ee commit 4710fdd

17 files changed

Lines changed: 453 additions & 77 deletions

File tree

‎compiler/rustc_codegen_llvm/src/attributes.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ pub(crate) fn default_optimisation_attrs<'ll>(
467467
sess: &Session,
468468
) -> SmallVec<[&'ll Attribute; 2]> {
469469
let mut attrs = SmallVec::new();
470+
// `s` and `z` request different attributes, which the objective cannot distinguish.
470471
match sess.opts.optimize {
471472
OptLevel::Size => {
472473
attrs.push(llvm::AttributeKind::OptimizeForSize.create_attr(cx.llcx));

‎compiler/rustc_codegen_llvm/src/back/write.rs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ pub(crate) fn create_target_machine(tcx: TyCtxt<'_>, mod_name: &str) -> OwnedTar
121121
target_machine_factory(tcx.sess, tcx.backend_optimization_level(()))(tcx.dcx(), config)
122122
}
123123

124+
// LLVM expresses "optimize for size" as a sub-setting of a numeric level, and `s` and `z` differ
125+
// there, so this keeps matching `OptLevel`.
124126
fn to_llvm_opt_settings(cfg: config::OptLevel) -> (llvm::CodeGenOptLevel, llvm::CodeGenOptSize) {
125127
use self::config::OptLevel::*;
126128
match cfg {
@@ -592,8 +594,7 @@ pub(crate) unsafe fn llvm_optimize<'ll>(
592594
vectorize_slp = false;
593595
vectorize_loop = false;
594596
} else {
595-
unroll_loops =
596-
opt_level != config::OptLevel::Size && opt_level != config::OptLevel::SizeMin;
597+
unroll_loops = opt_level.objective() != config::OptimizationObjective::Size;
597598
merge_functions = config.merge_functions;
598599
vectorize_slp = config.vectorize_slp;
599600
vectorize_loop = config.vectorize_loop;

‎compiler/rustc_codegen_ssa/src/back/linker.rs‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ impl<'a> GccLinker<'a> {
407407
self.link_arg(&arg);
408408
}
409409

410+
// This knob has no size setting: `s` and `z` both land on `O2`, as `More` does.
410411
let opt_level = match self.sess.opts.optimize {
411412
config::OptLevel::No => "O0",
412413
config::OptLevel::Less => "O1",
@@ -698,9 +699,7 @@ impl<'a> Linker for GccLinker<'a> {
698699

699700
// GNU-style linkers support optimization with -O. GNU ld doesn't
700701
// need a numeric argument, but other linkers do.
701-
if self.sess.opts.optimize == config::OptLevel::More
702-
|| self.sess.opts.optimize == config::OptLevel::Aggressive
703-
{
702+
if self.sess.optimization_objective() == config::OptimizationObjective::Speed {
704703
self.link_arg("-O1");
705704
}
706705
}
@@ -1021,7 +1020,7 @@ impl<'a> Linker for MsvcLinker<'a> {
10211020
// MSVC's ICF (Identical COMDAT Folding) link optimization is
10221021
// slow for Rust and thus we disable it by default when not in
10231022
// optimization build.
1024-
if self.sess.opts.optimize != config::OptLevel::No {
1023+
if self.sess.optimization_objective() != config::OptimizationObjective::None {
10251024
self.link_arg("/OPT:REF,ICF");
10261025
} else {
10271026
// It is necessary to specify NOICF here, because /OPT:REF
@@ -1249,7 +1248,7 @@ impl<'a> Linker for EmLinker<'a> {
12491248
}
12501249

12511250
fn optimize(&mut self) {
1252-
// Emscripten performs own optimizations
1251+
// Emscripten performs own optimizations.
12531252
self.cc_arg(match self.sess.opts.optimize {
12541253
OptLevel::No => "-O0",
12551254
OptLevel::Less => "-O1",

‎compiler/rustc_codegen_ssa/src/back/write.rs‎

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ use rustc_metadata::fs::copy_to_stdout;
2323
use rustc_middle::dep_graph::{WorkProduct, WorkProductMap};
2424
use rustc_middle::ty::TyCtxt;
2525
use rustc_session::config::{
26-
self, Lto, OptLevel, OutFileName, OutputFilenames, OutputType, Passes, SwitchWithOptPath,
26+
self, Lto, OptLevel, OptimizationObjective, OutFileName, OutputFilenames, OutputType, Passes,
27+
SwitchWithOptPath,
2728
};
2829
use rustc_session::{IncrCompSession, Session};
2930
use rustc_span::source_map::SourceMap;
@@ -228,10 +229,10 @@ impl ModuleConfig {
228229
no_builtins: no_builtins || sess.target.no_builtins,
229230

230231
// Copy what clang does by turning on loop vectorization at O2 and
231-
// slp vectorization at O3.
232+
// slp vectorization at O3. SLP is not an objective question: only the
233+
// highest level enables it.
232234
vectorize_loop: !sess.opts.cg.no_vectorize_loops
233-
&& (sess.opts.optimize == config::OptLevel::More
234-
|| sess.opts.optimize == config::OptLevel::Aggressive),
235+
&& sess.optimization_objective() == OptimizationObjective::Speed,
235236
vectorize_slp: !sess.opts.cg.no_vectorize_slp
236237
&& sess.opts.optimize == config::OptLevel::Aggressive,
237238

@@ -246,13 +247,10 @@ impl ModuleConfig {
246247
// O2 and O3) since it can be useful for reducing code size.
247248
merge_functions: match sess.merge_functions() {
248249
MergeFunctions::Disabled => false,
249-
MergeFunctions::Trampolines | MergeFunctions::Aliases => {
250-
use config::OptLevel::*;
251-
match sess.opts.optimize {
252-
Aggressive | More | SizeMin | Size => true,
253-
Less | No => false,
254-
}
255-
}
250+
MergeFunctions::Trampolines | MergeFunctions::Aliases => matches!(
251+
sess.optimization_objective(),
252+
OptimizationObjective::Speed | OptimizationObjective::Size
253+
),
256254
},
257255

258256
emit_lifetime_markers: sess.emit_lifetime_markers(),

‎compiler/rustc_codegen_ssa/src/base.rs‎

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,23 +1219,19 @@ impl CrateInfo {
12191219

12201220
pub(crate) fn provide(providers: &mut Providers) {
12211221
providers.backend_optimization_level = |tcx, cratenum| {
1222-
let for_speed = match tcx.sess.opts.optimize {
1223-
// If globally no optimisation is done, #[optimize] has no effect.
1224-
//
1225-
// This is done because if we ended up "upgrading" to `-O2` here, we’d populate the
1226-
// pass manager and it is likely that some module-wide passes (such as inliner or
1227-
// cross-function constant propagation) would ignore the `optnone` annotation we put
1228-
// on the functions, thus necessarily involving these functions into optimisations.
1229-
config::OptLevel::No => return config::OptLevel::No,
1230-
// If globally optimise-speed is already specified, just use that level.
1231-
config::OptLevel::Less => return config::OptLevel::Less,
1232-
config::OptLevel::More => return config::OptLevel::More,
1233-
config::OptLevel::Aggressive => return config::OptLevel::Aggressive,
1234-
// If globally optimize-for-size has been requested, use -O2 instead (if optimize(size)
1235-
// are present).
1236-
config::OptLevel::Size => config::OptLevel::More,
1237-
config::OptLevel::SizeMin => config::OptLevel::More,
1238-
};
1222+
let global = tcx.sess.opts.optimize;
1223+
1224+
// The global level is the answer, except when a body asks for speed inside a size build.
1225+
// If globally no optimization is done, `#[optimize]` has no effect: upgrading to `-O2`
1226+
// here would populate the pass manager, and module-wide passes (such as inliner or
1227+
// cross-function constant propagation) would then ignore the `optnone` annotation we put
1228+
// on the functions.
1229+
if global.objective() != config::OptimizationObjective::Size {
1230+
return global;
1231+
}
1232+
1233+
// There is no "size level, but faster", so `-O2` is the lowest level fulfilling it.
1234+
let for_speed = config::OptLevel::More;
12391235

12401236
let defids = tcx.collect_and_partition_mono_items(cratenum).all_mono_items;
12411237

@@ -1244,11 +1240,7 @@ pub(crate) fn provide(providers: &mut Providers) {
12441240
matches!(optimize, OptimizeAttr::Speed)
12451241
});
12461242

1247-
if any_for_speed {
1248-
return for_speed;
1249-
}
1250-
1251-
tcx.sess.opts.optimize
1243+
if any_for_speed { for_speed } else { global }
12521244
};
12531245
}
12541246

‎compiler/rustc_mir_transform/src/inline.rs‎

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_middle::mir::*;
1616
use rustc_middle::ty::{
1717
self, Instance, InstanceKind, ShimKind, Ty, TyCtxt, TypeFlags, TypeVisitableExt, Unnormalized,
1818
};
19-
use rustc_session::config::{DebugInfo, OptLevel};
19+
use rustc_session::config::{DebugInfo, OptimizationObjective};
2020
use rustc_span::{Spanned, bug};
2121
use tracing::{debug, instrument, trace, trace_span};
2222

@@ -44,20 +44,26 @@ pub struct Inline;
4444

4545
impl<'tcx> crate::MirPass<'tcx> for Inline {
4646
fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {
47-
match ctx.opts.unstable_opts.inline_mir {
48-
Some(enabled) => PassPolicy::optional(enabled),
49-
None => PassPolicy::optional(match ctx.mir_opt_level() {
50-
0 | 1 => false,
51-
// Only inline for `-Copt-level >= 2`, and don't inline
52-
// in incremental mode to increase incremental effectiveness.
53-
// FIXME: This should be cleaned up to not rely on inspecting the global opt level.
54-
2 => {
55-
(ctx.opts.optimize == OptLevel::More
56-
|| ctx.opts.optimize == OptLevel::Aggressive)
57-
&& ctx.opts.incremental.is_none()
47+
if let Some(o) = ctx.opts.unstable_opts.inline_mir {
48+
return PassPolicy::optional(o);
49+
}
50+
// `#[optimize]` on a body may lower the pass but not raise it, hence the `min`.
51+
match ctx.mir_opt_level().min(ctx.session_mir_opt_level()) {
52+
0 | 1 => PassPolicy::optional(false),
53+
// Don't inline in incremental mode: it hurts incremental effectiveness.
54+
2 => {
55+
if ctx.opts.incremental.is_some() {
56+
return PassPolicy::optional(false);
5857
}
59-
_ => true,
60-
}),
58+
match ctx.optimization_objective() {
59+
OptimizationObjective::Size => PassPolicy::size_only(),
60+
OptimizationObjective::Speed => PassPolicy::optional(true),
61+
OptimizationObjective::None | OptimizationObjective::Balanced => {
62+
PassPolicy::optional(false)
63+
}
64+
}
65+
}
66+
_ => PassPolicy::optional(true),
6167
}
6268
}
6369

‎compiler/rustc_mir_transform/src/pass_manager.rs‎

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_hir::def_id::DefId;
88
use rustc_middle::mir::{Body, MirDumper, MirPhase, RuntimePhase};
99
use rustc_middle::ty::TyCtxt;
1010
use rustc_session::Session;
11-
use rustc_session::config::OptLevel;
11+
use rustc_session::config::{OptLevel, OptimizationObjective};
1212
use rustc_span::bug;
1313
use tracing::trace;
1414

@@ -96,13 +96,31 @@ pub(crate) enum PassPolicy {
9696
/// Whether this pass should be enabled in the absence of an explicit
9797
/// `-Zmir-enable-passes` override.
9898
enabled_by_default: bool,
99+
/// Whether this pass trades runtime speed for binary size, and so is enabled only when the
100+
/// objective is size.
101+
size_only: bool,
99102
},
100103
}
101104

102105
impl PassPolicy {
103106
/// Create a [`PassPolicy::Optional`] enabled by default under the given condition.
104107
pub(crate) fn optional(enabled_by_default: bool) -> Self {
105-
Self::Optional { enabled_by_default }
108+
Self::Optional { enabled_by_default, size_only: false }
109+
}
110+
111+
/// Create a [`PassPolicy::Optional`] that is enabled only when size is the objective.
112+
pub(crate) fn size_only() -> Self {
113+
Self::Optional { enabled_by_default: false, size_only: true }
114+
}
115+
116+
fn runs_by_default(self, ctx: &PassCtx<'_>) -> bool {
117+
match self {
118+
PassPolicy::Required => true,
119+
PassPolicy::Optional { enabled_by_default, size_only } => {
120+
enabled_by_default
121+
|| (size_only && ctx.optimization_objective() == OptimizationObjective::Size)
122+
}
123+
}
106124
}
107125
}
108126

@@ -151,10 +169,18 @@ impl<'sess> PassCtx<'sess> {
151169
Self { session: tcx.sess, body_mir_opt_level }
152170
}
153171

154-
/// The effective MIR optimization level for this body, including `#[optimize]` overrides.
172+
/// The MIR optimization level for this body, possibly overridden by `#[optimize]`.
173+
///
174+
/// A pass deciding whether the compilation wants it should ask
175+
/// [`session_mir_opt_level`](Self::session_mir_opt_level) instead.
155176
pub(super) fn mir_opt_level(&self) -> usize {
156177
self.body_mir_opt_level
157178
}
179+
180+
/// The compilation's MIR optimization level, ignoring this body's `#[optimize]` override.
181+
pub(super) fn session_mir_opt_level(&self) -> usize {
182+
self.session.mir_opt_level()
183+
}
158184
}
159185

160186
impl std::ops::Deref for PassCtx<'_> {
@@ -218,8 +244,9 @@ where
218244
let policy = self.1.policy(ctx);
219245
match policy {
220246
PassPolicy::Required => bug!("required pass cannot be gated by an opt level"),
221-
PassPolicy::Optional { enabled_by_default } => PassPolicy::Optional {
247+
PassPolicy::Optional { enabled_by_default, size_only } => PassPolicy::Optional {
222248
enabled_by_default: enabled_by_default && ctx.mir_opt_level() >= self.0,
249+
size_only,
223250
},
224251
}
225252
}
@@ -260,21 +287,26 @@ where
260287
.find_map(|(name_, polarity)| if name == name_ { Some(*polarity) } else { None })
261288
};
262289

263-
match pass.policy(ctx) {
264-
PassPolicy::Required => true,
265-
PassPolicy::Optional { enabled_by_default } => {
266-
if let Some(o) = pass_override() {
267-
trace!(
268-
pass = %name,
269-
"{} as requested by flag",
270-
if o { "Running" } else { "Not running" }
271-
);
272-
o
273-
} else {
274-
enabled_by_default
275-
}
276-
}
290+
let policy = pass.policy(ctx);
291+
292+
// Only `Optional` passes consult `-Zmir-enable-passes`; `Required` ones implement MIR semantics
293+
// and cannot be turned off.
294+
if let PassPolicy::Optional { .. } = policy
295+
&& let Some(o) = pass_override()
296+
{
297+
trace!(
298+
pass = %name,
299+
"{} as requested by flag",
300+
if o { "Running" } else { "Not running" }
301+
);
302+
return o;
303+
}
304+
305+
let runs = policy.runs_by_default(ctx);
306+
if !runs {
307+
trace!(pass = %name, "Not running: not enabled by default for this body");
277308
}
309+
runs
278310
}
279311

280312
fn run_passes_inner<'tcx>(

‎compiler/rustc_session/src/config.rs‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,38 @@ impl OptLevel {
126126
_ => 2,
127127
}
128128
}
129+
130+
/// Maps a general opt-level onto the objective it is trying to achieve.
131+
pub fn objective(&self) -> OptimizationObjective {
132+
match self {
133+
OptLevel::No => OptimizationObjective::None,
134+
OptLevel::Less => OptimizationObjective::Balanced,
135+
OptLevel::More | OptLevel::Aggressive => OptimizationObjective::Speed,
136+
OptLevel::Size | OptLevel::SizeMin => OptimizationObjective::Size,
137+
}
138+
}
139+
}
140+
141+
/// What a compilation, or a single item, is being asked to optimize *for*.
142+
///
143+
/// An [`OptLevel`] answers two questions at once: how much work to do, and what that work should
144+
/// achieve. `-Copt-level=s`/`z` are not below the numbered levels, they are a different objective
145+
/// that happens to be expressible as a level, so there is no order between [`Speed`] and [`Size`].
146+
/// Sites that map a level onto an LLVM or linker flag, where `s` and `z` produce *different* flags,
147+
/// still match on [`OptLevel`].
148+
///
149+
/// [`Speed`]: OptimizationObjective::Speed
150+
/// [`Size`]: OptimizationObjective::Size
151+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, StableHash)]
152+
pub enum OptimizationObjective {
153+
/// Nothing is optimized, so there is no objective (`-Copt-level=0`).
154+
None,
155+
/// Optimize, without a strong preference for speed over size (`-Copt-level=1`).
156+
Balanced,
157+
/// Favor runtime speed over binary size (`-Copt-level=2`/`3`, `#[optimize(speed)]`).
158+
Speed,
159+
/// Favor binary size over runtime speed (`-Copt-level=s`/`z`, `#[optimize(size)]`).
160+
Size,
129161
}
130162

131163
/// This is what the `LtoCli` values get mapped to after resolving defaults and
@@ -1568,10 +1600,9 @@ impl Options {
15681600
pub fn share_generics(&self) -> bool {
15691601
match self.unstable_opts.share_generics {
15701602
Some(setting) => setting,
1571-
None => match self.optimize {
1572-
OptLevel::No | OptLevel::Less | OptLevel::Size | OptLevel::SizeMin => true,
1573-
OptLevel::More | OptLevel::Aggressive => false,
1574-
},
1603+
// Sharing instantiations means fewer copies, at the cost of leaving them opaque to
1604+
// speed optimizations. That is a good trade unless speed is what is asked for.
1605+
None => self.optimize.objective() != OptimizationObjective::Speed,
15751606
}
15761607
}
15771608

‎compiler/rustc_session/src/session.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,15 @@ impl Session {
983983
self.opts.unstable_opts.mir_opt_level.unwrap_or_else(|| self.opts.optimize.mir_opt_level())
984984
}
985985

986+
/// The objective this compilation is optimizing for. See
987+
/// [`config::OptimizationObjective`].
988+
///
989+
/// There is no `-Z` override for this, unlike [`Session::mir_opt_level`]: how much MIR
990+
/// optimization is done is orthogonal to what it is optimizing for.
991+
pub fn optimization_objective(&self) -> config::OptimizationObjective {
992+
self.opts.optimize.objective()
993+
}
994+
986995
/// Calculates the flavor of LTO to use for this compilation.
987996
pub fn lto(&self) -> config::Lto {
988997
// If our target has codegen requirements ignore the command line

0 commit comments

Comments
 (0)