Skip to content

Commit 4b53279

Browse files
committed
Auto merge of #148040 - saethlin:trivial-consts, r=oli-obk
Add a fast path for lowering trivial consts The objective of this PR is to improve compilation performance for crates that define a lot of trivial consts. This is a flamegraph of a build of a library crate that is just 100,000 trivial consts, taken from a nightly compiler: <img width="842" height="280" alt="2025-10-25-164005_842x280_scrot" src="https://github.com/user-attachments/assets/e5400aaf-03bd-4461-b905-054aa82ca60f" /> My objective is to target all of the cycles in `eval_to_const_value_raw` that are not part of `mir_built`, because if you look at the `mir_built` for a trivial const, we already have the value available. In this PR, the definition of a trivial const is this: ```rust const A: usize = 0; ``` Specifically, we look for if the `mir_built` body is a single basic block containing one assign statement and a return terminator, where the assign statement assigns an `Operand::Constant(Const::Val)`. The MIR dumps for these look like: ``` const A: usize = { let mut _0: usize; bb0: { _0 = const 0_usize; return; } } ``` The implementation is built around a new query, `trivial_const(LocalDefId) -> Option<(ConstValue, Ty)>` which returns the contents of the `Const::Val` in the `mir_built` if the `LocalDefId` is a trivial const. Then I added _debug_ assertions to the beginning of `mir_for_ctfe` and `mir_promoted` to prevent trying to get the body of a trivial const, because that would defeat the optimization here. But these are deliberately _debug_ assertions because the consequence of failing the assertion is that compilation is slow, not corrupt. If we made these hard assertions, I'm sure there are obscure scenarios people will run into where the compiler would ICE instead of continuing on compilation, just a bit slower. I'd like to know about those, but I do not think serving up an ICE is worth it. With the assertions in place, I just added logic around all the places they were hit, to skip over trying to analyze the bodies of trivial consts. In the future, I'd like to see this work extended by: * Pushing detection of trivial consts before MIR building * Including DefKind::Static and DefKind::InlineConst * Including consts like `_1 = const 0_usize; _0 = &_1`, which would make a lot of promoteds into trivial consts * Handling less-trivial consts like `const A: usize = B`, which have `Operand::Constant(Const::Unevaluated)`
2 parents 34a8c73 + a63035f commit 4b53279

File tree

17 files changed

+231
-79
lines changed

17 files changed

+231
-79
lines changed

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_hir::def::DefKind;
77
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, InterpErrorInfo, ReportedErrorInfo};
88
use rustc_middle::mir::{self, ConstAlloc, ConstValue};
99
use rustc_middle::query::TyCtxtAt;
10-
use rustc_middle::ty::layout::HasTypingEnv;
10+
use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout};
1111
use rustc_middle::ty::print::with_no_trimmed_paths;
1212
use rustc_middle::ty::{self, Ty, TyCtxt};
1313
use rustc_middle::{bug, throw_inval};
@@ -24,13 +24,11 @@ use crate::interpret::{
2424
};
2525
use crate::{CTRL_C_RECEIVED, errors};
2626

27-
// Returns a pointer to where the result lives
28-
#[instrument(level = "trace", skip(ecx, body))]
29-
fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
27+
fn setup_for_eval<'tcx>(
3028
ecx: &mut CompileTimeInterpCx<'tcx>,
3129
cid: GlobalId<'tcx>,
32-
body: &'tcx mir::Body<'tcx>,
33-
) -> InterpResult<'tcx, R> {
30+
layout: TyAndLayout<'tcx>,
31+
) -> InterpResult<'tcx, (InternKind, MPlaceTy<'tcx>)> {
3432
let tcx = *ecx.tcx;
3533
assert!(
3634
cid.promoted.is_some()
@@ -46,7 +44,6 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
4644
"Unexpected DefKind: {:?}",
4745
ecx.tcx.def_kind(cid.instance.def_id())
4846
);
49-
let layout = ecx.layout_of(body.bound_return_ty().instantiate(tcx, cid.instance.args))?;
5047
assert!(layout.is_sized());
5148

5249
let intern_kind = if cid.promoted.is_some() {
@@ -58,12 +55,25 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
5855
}
5956
};
6057

61-
let ret = if let InternKind::Static(_) = intern_kind {
62-
create_static_alloc(ecx, cid.instance.def_id().expect_local(), layout)?
58+
let return_place = if let InternKind::Static(_) = intern_kind {
59+
create_static_alloc(ecx, cid.instance.def_id().expect_local(), layout)
6360
} else {
64-
ecx.allocate(layout, MemoryKind::Stack)?
61+
ecx.allocate(layout, MemoryKind::Stack)
6562
};
6663

64+
return_place.map(|ret| (intern_kind, ret))
65+
}
66+
67+
#[instrument(level = "trace", skip(ecx, body))]
68+
fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
69+
ecx: &mut CompileTimeInterpCx<'tcx>,
70+
cid: GlobalId<'tcx>,
71+
body: &'tcx mir::Body<'tcx>,
72+
) -> InterpResult<'tcx, R> {
73+
let tcx = *ecx.tcx;
74+
let layout = ecx.layout_of(body.bound_return_ty().instantiate(tcx, cid.instance.args))?;
75+
let (intern_kind, ret) = setup_for_eval(ecx, cid, layout)?;
76+
6777
trace!(
6878
"eval_body_using_ecx: pushing stack frame for global: {}{}",
6979
with_no_trimmed_paths!(ecx.tcx.def_path_str(cid.instance.def_id())),
@@ -87,6 +97,31 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
8797
}
8898
}
8999

100+
intern_and_validate(ecx, cid, intern_kind, ret)
101+
}
102+
103+
#[instrument(level = "trace", skip(ecx))]
104+
fn eval_trivial_const_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
105+
ecx: &mut CompileTimeInterpCx<'tcx>,
106+
cid: GlobalId<'tcx>,
107+
val: ConstValue,
108+
ty: Ty<'tcx>,
109+
) -> InterpResult<'tcx, R> {
110+
let layout = ecx.layout_of(ty)?;
111+
let (intern_kind, return_place) = setup_for_eval(ecx, cid, layout)?;
112+
113+
let opty = ecx.const_val_to_op(val, ty, Some(layout))?;
114+
ecx.copy_op(&opty, &return_place)?;
115+
116+
intern_and_validate(ecx, cid, intern_kind, return_place)
117+
}
118+
119+
fn intern_and_validate<'tcx, R: InterpretationResult<'tcx>>(
120+
ecx: &mut CompileTimeInterpCx<'tcx>,
121+
cid: GlobalId<'tcx>,
122+
intern_kind: InternKind,
123+
ret: MPlaceTy<'tcx>,
124+
) -> InterpResult<'tcx, R> {
90125
// Intern the result
91126
let intern_result = intern_const_alloc_recursive(ecx, intern_kind, &ret);
92127

@@ -292,6 +327,9 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
292327
tcx: TyCtxt<'tcx>,
293328
key: ty::PseudoCanonicalInput<'tcx, GlobalId<'tcx>>,
294329
) -> ::rustc_middle::mir::interpret::EvalToConstValueResult<'tcx> {
330+
if let Some((value, _ty)) = tcx.trivial_const(key.value.instance.def_id()) {
331+
return Ok(value);
332+
}
295333
tcx.eval_to_allocation_raw(key).map(|val| turn_into_const_value(tcx, val, key))
296334
}
297335

@@ -368,10 +406,14 @@ fn eval_in_interpreter<'tcx, R: InterpretationResult<'tcx>>(
368406
// so we have to reject reading mutable global memory.
369407
CompileTimeMachine::new(CanAccessMutGlobal::from(is_static), CheckAlignment::Error),
370408
);
371-
let res = ecx.load_mir(cid.instance.def, cid.promoted);
372-
res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body))
373-
.report_err()
374-
.map_err(|error| report_eval_error(&ecx, cid, error))
409+
410+
let result = if let Some((value, ty)) = tcx.trivial_const(def) {
411+
eval_trivial_const_using_ecx(&mut ecx, cid, value, ty)
412+
} else {
413+
ecx.load_mir(cid.instance.def, cid.promoted)
414+
.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body))
415+
};
416+
result.report_err().map_err(|error| report_eval_error(&ecx, cid, error))
375417
}
376418

377419
#[inline(always)]

compiler/rustc_interface/src/passes.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,9 +1088,15 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
10881088

10891089
sess.time("MIR_borrow_checking", || {
10901090
tcx.par_hir_body_owners(|def_id| {
1091-
if !tcx.is_typeck_child(def_id.to_def_id()) {
1091+
let not_typeck_child = !tcx.is_typeck_child(def_id.to_def_id());
1092+
if not_typeck_child {
10921093
// Child unsafety and borrowck happens together with the parent
10931094
tcx.ensure_ok().check_unsafety(def_id);
1095+
}
1096+
if tcx.is_trivial_const(def_id) {
1097+
return;
1098+
}
1099+
if not_typeck_child {
10941100
tcx.ensure_ok().mir_borrowck(def_id);
10951101
tcx.ensure_ok().check_transmutes(def_id);
10961102
}
@@ -1198,7 +1204,9 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) {
11981204
if tcx.sess.opts.unstable_opts.validate_mir {
11991205
sess.time("ensuring_final_MIR_is_computable", || {
12001206
tcx.par_hir_body_owners(|def_id| {
1201-
tcx.instance_mir(ty::InstanceKind::Item(def_id.into()));
1207+
if !tcx.is_trivial_const(def_id) {
1208+
tcx.instance_mir(ty::InstanceKind::Item(def_id.into()));
1209+
}
12021210
});
12031211
});
12041212
}

compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ provide! { tcx, def_id, other, cdata,
240240
thir_abstract_const => { table }
241241
optimized_mir => { table }
242242
mir_for_ctfe => { table }
243+
trivial_const => { table }
243244
closure_saved_names_of_captured_variables => { table }
244245
mir_coroutine_witnesses => { table }
245246
promoted_mir => { table }

compiler/rustc_metadata/src/rmeta/encoder.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,8 +1791,15 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
17911791
record!(self.tables.mir_coroutine_witnesses[def_id.to_def_id()] <- witnesses);
17921792
}
17931793
}
1794+
let mut is_trivial = false;
17941795
if encode_const {
1795-
record!(self.tables.mir_for_ctfe[def_id.to_def_id()] <- tcx.mir_for_ctfe(def_id));
1796+
if let Some((val, ty)) = tcx.trivial_const(def_id) {
1797+
is_trivial = true;
1798+
record!(self.tables.trivial_const[def_id.to_def_id()] <- (val, ty));
1799+
} else {
1800+
is_trivial = false;
1801+
record!(self.tables.mir_for_ctfe[def_id.to_def_id()] <- tcx.mir_for_ctfe(def_id));
1802+
}
17961803

17971804
// FIXME(generic_const_exprs): this feels wrong to have in `encode_mir`
17981805
let abstract_const = tcx.thir_abstract_const(def_id);
@@ -1810,7 +1817,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
18101817
}
18111818
}
18121819
}
1813-
record!(self.tables.promoted_mir[def_id.to_def_id()] <- tcx.promoted_mir(def_id));
1820+
if !is_trivial {
1821+
record!(self.tables.promoted_mir[def_id.to_def_id()] <- tcx.promoted_mir(def_id));
1822+
}
18141823

18151824
if self.tcx.is_coroutine(def_id.to_def_id())
18161825
&& let Some(witnesses) = tcx.mir_coroutine_witnesses(def_id)
@@ -2234,6 +2243,9 @@ fn prefetch_mir(tcx: TyCtxt<'_>) {
22342243

22352244
let reachable_set = tcx.reachable_set(());
22362245
par_for_each_in(tcx.mir_keys(()), |&&def_id| {
2246+
if tcx.is_trivial_const(def_id) {
2247+
return;
2248+
}
22372249
let (encode_const, encode_opt) = should_encode_mir(tcx, reachable_set, def_id);
22382250

22392251
if encode_const {

compiler/rustc_metadata/src/rmeta/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo};
2929
use rustc_middle::middle::lib_features::FeatureStability;
3030
use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault;
3131
use rustc_middle::mir;
32+
use rustc_middle::mir::ConstValue;
3233
use rustc_middle::ty::fast_reject::SimplifiedType;
3334
use rustc_middle::ty::{self, Ty, TyCtxt, UnusedGenericParams};
3435
use rustc_middle::util::Providers;
@@ -426,6 +427,7 @@ define_tables! {
426427
object_lifetime_default: Table<DefIndex, LazyValue<ObjectLifetimeDefault>>,
427428
optimized_mir: Table<DefIndex, LazyValue<mir::Body<'static>>>,
428429
mir_for_ctfe: Table<DefIndex, LazyValue<mir::Body<'static>>>,
430+
trivial_const: Table<DefIndex, LazyValue<(ConstValue, Ty<'static>)>>,
429431
closure_saved_names_of_captured_variables: Table<DefIndex, LazyValue<IndexVec<FieldIdx, Symbol>>>,
430432
mir_coroutine_witnesses: Table<DefIndex, LazyValue<mir::CoroutineLayout<'static>>>,
431433
promoted_mir: Table<DefIndex, LazyValue<IndexVec<mir::Promoted, mir::Body<'static>>>>,

compiler/rustc_metadata/src/rmeta/parameterized.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ trivially_parameterized_over_tcx! {
102102
rustc_middle::middle::lib_features::FeatureStability,
103103
rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault,
104104
rustc_middle::mir::ConstQualifs,
105+
rustc_middle::mir::ConstValue,
105106
rustc_middle::ty::AnonConstKind,
106107
rustc_middle::ty::AssocContainer,
107108
rustc_middle::ty::AsyncDestructor,

compiler/rustc_middle/src/mir/graphviz.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ where
1616

1717
let mirs = def_ids
1818
.iter()
19+
.filter(|def_id| !tcx.is_trivial_const(*def_id))
1920
.flat_map(|def_id| {
2021
if tcx.is_const_fn(*def_id) {
2122
vec![tcx.optimized_mir(*def_id), tcx.mir_for_ctfe(*def_id)]

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,16 @@ pub fn write_mir_pretty<'tcx>(
353353
// are shared between mir_for_ctfe and optimized_mir
354354
writer.write_mir_fn(tcx.mir_for_ctfe(def_id), w)?;
355355
} else {
356-
let instance_mir = tcx.instance_mir(ty::InstanceKind::Item(def_id));
357-
render_body(w, instance_mir)?;
356+
if let Some((val, ty)) = tcx.trivial_const(def_id) {
357+
ty::print::with_forced_impl_filename_line! {
358+
// see notes on #41697 elsewhere
359+
write!(w, "const {}", tcx.def_path_str(def_id))?
360+
}
361+
writeln!(w, ": {} = const {};", ty, Const::Val(val, ty))?;
362+
} else {
363+
let instance_mir = tcx.instance_mir(ty::InstanceKind::Item(def_id));
364+
render_body(w, instance_mir)?;
365+
}
358366
}
359367
}
360368
Ok(())

compiler/rustc_middle/src/query/erase.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ impl EraseType for Result<mir::ConstValue, mir::interpret::ErrorHandled> {
160160
type Result = [u8; size_of::<Result<mir::ConstValue, mir::interpret::ErrorHandled>>()];
161161
}
162162

163+
impl EraseType for Option<(mir::ConstValue, Ty<'_>)> {
164+
type Result = [u8; size_of::<Option<(mir::ConstValue, Ty<'_>)>>()];
165+
}
166+
163167
impl EraseType for EvalToValTreeResult<'_> {
164168
type Result = [u8; size_of::<EvalToValTreeResult<'static>>()];
165169
}

compiler/rustc_middle/src/query/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,6 +2719,12 @@ rustc_queries! {
27192719
separate_provide_extern
27202720
}
27212721

2722+
query trivial_const(def_id: DefId) -> Option<(mir::ConstValue, Ty<'tcx>)> {
2723+
desc { |tcx| "checking if `{}` is a trivial const", tcx.def_path_str(def_id) }
2724+
cache_on_disk_if { def_id.is_local() }
2725+
separate_provide_extern
2726+
}
2727+
27222728
/// Checks for the nearest `#[sanitize(xyz = "off")]` or
27232729
/// `#[sanitize(xyz = "on")]` on this def and any enclosing defs, up to the
27242730
/// crate root.

0 commit comments

Comments
 (0)