Skip to content

Commit 9d9c7ad

Browse files
committed
Auto merge of rust-lang#61492 - RalfJung:const-qualif-comments, r=eddyb
Const qualification comments I extracted some const-qualif knowledge from @eddyb. This is my attempt to turn that into comments. Cc @oli-obk @eddyb
2 parents efc30d0 + 0edf46f commit 9d9c7ad

File tree

1 file changed

+81
-43
lines changed

1 file changed

+81
-43
lines changed

src/librustc_mir/transform/qualify_consts.rs

+81-43
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,25 @@ use super::promote_consts::{self, Candidate, TempState};
3535
/// What kind of item we are in.
3636
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3737
enum Mode {
38-
Const,
38+
/// A `static` item.
3939
Static,
40+
/// A `static mut` item.
4041
StaticMut,
42+
/// A `const fn` item.
4143
ConstFn,
42-
Fn
44+
/// A `const` item or an anonymous constant (e.g. in array lengths).
45+
Const,
46+
/// Other type of `fn`.
47+
NonConstFn,
48+
}
49+
50+
impl Mode {
51+
/// Determine whether we have to do full const-checking because syntactically, we
52+
/// are required to be "const".
53+
#[inline]
54+
fn requires_const_checking(self) -> bool {
55+
self != Mode::NonConstFn
56+
}
4357
}
4458

4559
impl fmt::Display for Mode {
@@ -48,7 +62,7 @@ impl fmt::Display for Mode {
4862
Mode::Const => write!(f, "constant"),
4963
Mode::Static | Mode::StaticMut => write!(f, "static"),
5064
Mode::ConstFn => write!(f, "constant function"),
51-
Mode::Fn => write!(f, "function")
65+
Mode::NonConstFn => write!(f, "function")
5266
}
5367
}
5468
}
@@ -135,6 +149,12 @@ enum ValueSource<'a, 'tcx> {
135149
},
136150
}
137151

152+
/// A "qualif"(-ication) is a way to look for something "bad" in the MIR that would disqualify some
153+
/// code for promotion or prevent it from evaluating at compile time. So `return true` means
154+
/// "I found something bad, no reason to go on searching". `false` is only returned if we
155+
/// definitely cannot find anything bad anywhere.
156+
///
157+
/// The default implementations proceed structurally.
138158
trait Qualif {
139159
const IDX: usize;
140160

@@ -285,7 +305,11 @@ trait Qualif {
285305
}
286306
}
287307

288-
// Constant containing interior mutability (UnsafeCell).
308+
/// Constant containing interior mutability (`UnsafeCell<T>`).
309+
/// This must be ruled out to make sure that evaluating the constant at compile-time
310+
/// and at *any point* during the run-time would produce the same result. In particular,
311+
/// promotion of temporaries must not change program behavior; if the promoted could be
312+
/// written to, that would be a problem.
289313
struct HasMutInterior;
290314

291315
impl Qualif for HasMutInterior {
@@ -314,10 +338,10 @@ impl Qualif for HasMutInterior {
314338
_ => return true,
315339
}
316340
} else if let ty::Array(_, len) = ty.sty {
317-
// FIXME(eddyb) the `cx.mode == Mode::Fn` condition
341+
// FIXME(eddyb) the `cx.mode == Mode::NonConstFn` condition
318342
// seems unnecessary, given that this is merely a ZST.
319343
match len.assert_usize(cx.tcx) {
320-
Some(0) if cx.mode == Mode::Fn => {},
344+
Some(0) if cx.mode == Mode::NonConstFn => {},
321345
_ => return true,
322346
}
323347
} else {
@@ -343,7 +367,10 @@ impl Qualif for HasMutInterior {
343367
}
344368
}
345369

346-
// Constant containing an ADT that implements Drop.
370+
/// Constant containing an ADT that implements `Drop`.
371+
/// This must be ruled out (a) because we cannot run `Drop` during compile-time
372+
/// as that might not be a `const fn`, and (b) because implicit promotion would
373+
/// remove side-effects that occur as part of dropping that value.
347374
struct NeedsDrop;
348375

349376
impl Qualif for NeedsDrop {
@@ -366,8 +393,12 @@ impl Qualif for NeedsDrop {
366393
}
367394
}
368395

369-
// Not promotable at all - non-`const fn` calls, asm!,
370-
// pointer comparisons, ptr-to-int casts, etc.
396+
/// Not promotable at all - non-`const fn` calls, `asm!`,
397+
/// pointer comparisons, ptr-to-int casts, etc.
398+
/// Inside a const context all constness rules apply, so promotion simply has to follow the regular
399+
/// constant rules (modulo interior mutability or `Drop` rules which are handled `HasMutInterior`
400+
/// and `NeedsDrop` respectively). Basically this duplicates the checks that the const-checking
401+
/// visitor enforces by emitting errors when working in const context.
371402
struct IsNotPromotable;
372403

373404
impl Qualif for IsNotPromotable {
@@ -398,9 +429,10 @@ impl Qualif for IsNotPromotable {
398429
ProjectionElem::Index(_) => {}
399430

400431
ProjectionElem::Field(..) => {
401-
if cx.mode == Mode::Fn {
432+
if cx.mode == Mode::NonConstFn {
402433
let base_ty = proj.base.ty(cx.body, cx.tcx).ty;
403434
if let Some(def) = base_ty.ty_adt_def() {
435+
// No promotion of union field accesses.
404436
if def.is_union() {
405437
return true;
406438
}
@@ -414,7 +446,7 @@ impl Qualif for IsNotPromotable {
414446

415447
fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool {
416448
match *rvalue {
417-
Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) if cx.mode == Mode::Fn => {
449+
Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) if cx.mode == Mode::NonConstFn => {
418450
let operand_ty = operand.ty(cx.body, cx.tcx);
419451
let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
420452
let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
@@ -428,7 +460,7 @@ impl Qualif for IsNotPromotable {
428460
}
429461
}
430462

431-
Rvalue::BinaryOp(op, ref lhs, _) if cx.mode == Mode::Fn => {
463+
Rvalue::BinaryOp(op, ref lhs, _) if cx.mode == Mode::NonConstFn => {
432464
if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(cx.body, cx.tcx).sty {
433465
assert!(op == BinOp::Eq || op == BinOp::Ne ||
434466
op == BinOp::Le || op == BinOp::Lt ||
@@ -511,12 +543,9 @@ impl Qualif for IsNotPromotable {
511543

512544
/// Refers to temporaries which cannot be promoted *implicitly*.
513545
/// Explicit promotion happens e.g. for constant arguments declared via `rustc_args_required_const`.
514-
/// Inside a const context all constness rules
515-
/// apply, so implicit promotion simply has to follow the regular constant rules (modulo interior
516-
/// mutability or `Drop` rules which are handled `HasMutInterior` and `NeedsDrop` respectively).
517-
/// Implicit promotion inside regular functions does not happen if `const fn` calls are involved,
518-
/// as the call may be perfectly alright at runtime, but fail at compile time e.g. due to addresses
519-
/// being compared inside the function.
546+
/// Implicit promotion has almost the same rules, except that disallows `const fn` except for
547+
/// those marked `#[rustc_promotable]`. This is to avoid changing a legitimate run-time operation
548+
/// into a failing compile-time operation e.g. due to addresses being compared inside the function.
520549
struct IsNotImplicitlyPromotable;
521550

522551
impl Qualif for IsNotImplicitlyPromotable {
@@ -528,7 +557,7 @@ impl Qualif for IsNotImplicitlyPromotable {
528557
args: &[Operand<'tcx>],
529558
_return_ty: Ty<'tcx>,
530559
) -> bool {
531-
if cx.mode == Mode::Fn {
560+
if cx.mode == Mode::NonConstFn {
532561
if let ty::FnDef(def_id, _) = callee.ty(cx.body, cx.tcx).sty {
533562
// Never promote runtime `const fn` calls of
534563
// functions without `#[rustc_promotable]`.
@@ -589,6 +618,11 @@ impl ConstCx<'_, 'tcx> {
589618
}
590619
}
591620

621+
/// Checks MIR for being admissible as a compile-time constant, using `ConstCx`
622+
/// for value qualifications, and accumulates writes of
623+
/// rvalue/call results to locals, in `local_qualif`.
624+
/// It also records candidates for promotion in `promotion_candidates`,
625+
/// both in functions and const/static items.
592626
struct Checker<'a, 'tcx> {
593627
cx: ConstCx<'a, 'tcx>,
594628

@@ -672,7 +706,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
672706
// slightly pointless (even with feature-gating).
673707
fn not_const(&mut self) {
674708
unleash_miri!(self);
675-
if self.mode != Mode::Fn {
709+
if self.mode.requires_const_checking() {
676710
let mut err = struct_span_err!(
677711
self.tcx.sess,
678712
self.span,
@@ -707,7 +741,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
707741
qualifs[HasMutInterior] = false;
708742
qualifs[IsNotPromotable] = true;
709743

710-
if self.mode != Mode::Fn {
744+
if self.mode.requires_const_checking() {
711745
if let BorrowKind::Mut { .. } = kind {
712746
let mut err = struct_span_err!(self.tcx.sess, self.span, E0017,
713747
"references in {}s may only refer \
@@ -737,7 +771,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
737771

738772
// We might have a candidate for promotion.
739773
let candidate = Candidate::Ref(location);
740-
// We can only promote interior borrows of promotable temps.
774+
// Start by traversing to the "base", with non-deref projections removed.
741775
let mut place = place;
742776
while let Place::Projection(ref proj) = *place {
743777
if proj.elem == ProjectionElem::Deref {
@@ -746,6 +780,10 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
746780
place = &proj.base;
747781
}
748782
debug!("qualify_consts: promotion candidate: place={:?}", place);
783+
// We can only promote interior borrows of promotable temps (non-temps
784+
// don't get promoted anyway).
785+
// (If we bailed out of the loop due to a `Deref` above, we will definitely
786+
// not enter the conditional here.)
749787
if let Place::Base(PlaceBase::Local(local)) = *place {
750788
if self.body.local_kind(local) == LocalKind::Temp {
751789
debug!("qualify_consts: promotion candidate: local={:?}", local);
@@ -756,6 +794,10 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
756794
// `HasMutInterior`, from a type that does, e.g.:
757795
// `let _: &'static _ = &(Cell::new(1), 2).1;`
758796
let mut local_qualifs = self.qualifs_in_local(local);
797+
// Any qualifications, except HasMutInterior (see above), disqualify
798+
// from promotion.
799+
// This is, in particular, the "implicit promotion" version of
800+
// the check making sure that we don't run drop glue during const-eval.
759801
local_qualifs[HasMutInterior] = false;
760802
if !local_qualifs.0.iter().any(|&qualif| qualif) {
761803
debug!("qualify_consts: promotion candidate: {:?}", candidate);
@@ -803,7 +845,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
803845
debug!("store to {:?} {:?}", kind, index);
804846

805847
// Only handle promotable temps in non-const functions.
806-
if self.mode == Mode::Fn {
848+
if self.mode == Mode::NonConstFn {
807849
if kind != LocalKind::Temp ||
808850
!self.temp_promotion_state[index].is_promotable() {
809851
return;
@@ -920,11 +962,6 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
920962
}
921963
}
922964

923-
/// Checks MIR for const-correctness, using `ConstCx`
924-
/// for value qualifications, and accumulates writes of
925-
/// rvalue/call results to locals, in `local_qualif`.
926-
/// For functions (constant or not), it also records
927-
/// candidates for promotion in `promotion_candidates`.
928965
impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
929966
fn visit_place_base(
930967
&mut self,
@@ -943,7 +980,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
943980
.get_attrs(*def_id)
944981
.iter()
945982
.any(|attr| attr.check_name(sym::thread_local)) {
946-
if self.mode != Mode::Fn {
983+
if self.mode.requires_const_checking() {
947984
span_err!(self.tcx.sess, self.span, E0625,
948985
"thread-local statics cannot be \
949986
accessed at compile-time");
@@ -967,7 +1004,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
9671004
}
9681005
unleash_miri!(self);
9691006

970-
if self.mode != Mode::Fn {
1007+
if self.mode.requires_const_checking() {
9711008
let mut err = struct_span_err!(self.tcx.sess, self.span, E0013,
9721009
"{}s cannot refer to statics, use \
9731010
a constant instead", self.mode);
@@ -1005,7 +1042,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
10051042
}
10061043
let base_ty = proj.base.ty(self.body, self.tcx).ty;
10071044
match self.mode {
1008-
Mode::Fn => {},
1045+
Mode::NonConstFn => {},
10091046
_ => {
10101047
if let ty::RawPtr(_) = base_ty.sty {
10111048
if !self.tcx.features().const_raw_ptr_deref {
@@ -1041,7 +1078,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
10411078
}
10421079
},
10431080

1044-
| Mode::Fn
1081+
| Mode::NonConstFn
10451082
| Mode::Static
10461083
| Mode::StaticMut
10471084
| Mode::Const
@@ -1131,7 +1168,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
11311168
let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
11321169
match (cast_in, cast_out) {
11331170
(CastTy::Ptr(_), CastTy::Int(_)) |
1134-
(CastTy::FnPtr, CastTy::Int(_)) if self.mode != Mode::Fn => {
1171+
(CastTy::FnPtr, CastTy::Int(_)) if self.mode != Mode::NonConstFn => {
11351172
unleash_miri!(self);
11361173
if !self.tcx.features().const_raw_ptr_to_usize_cast {
11371174
// in const fn and constants require the feature gate
@@ -1158,7 +1195,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
11581195
op == BinOp::Offset);
11591196

11601197
unleash_miri!(self);
1161-
if self.mode != Mode::Fn && !self.tcx.features().const_compare_raw_pointers {
1198+
if self.mode.requires_const_checking() &&
1199+
!self.tcx.features().const_compare_raw_pointers
1200+
{
11621201
// require the feature gate inside constants and const fn
11631202
// FIXME: make it unsafe to use these operations
11641203
emit_feature_err(
@@ -1174,7 +1213,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
11741213

11751214
Rvalue::NullaryOp(NullOp::Box, _) => {
11761215
unleash_miri!(self);
1177-
if self.mode != Mode::Fn {
1216+
if self.mode.requires_const_checking() {
11781217
let mut err = struct_span_err!(self.tcx.sess, self.span, E0010,
11791218
"allocations are not allowed in {}s", self.mode);
11801219
err.span_label(self.span, format!("allocation not allowed in {}s", self.mode));
@@ -1219,8 +1258,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
12191258
// special intrinsic that can be called diretly without an intrinsic
12201259
// feature gate needs a language feature gate
12211260
"transmute" => {
1222-
// never promote transmute calls
1223-
if self.mode != Mode::Fn {
1261+
if self.mode.requires_const_checking() {
12241262
// const eval transmute calls only with the feature gate
12251263
if !self.tcx.features().const_transmute {
12261264
emit_feature_err(
@@ -1243,7 +1281,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
12431281
}
12441282
_ => {
12451283
// In normal functions no calls are feature-gated.
1246-
if self.mode != Mode::Fn {
1284+
if self.mode.requires_const_checking() {
12471285
let unleash_miri = self
12481286
.tcx
12491287
.sess
@@ -1302,7 +1340,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
13021340
}
13031341
}
13041342
ty::FnPtr(_) => {
1305-
if self.mode != Mode::Fn {
1343+
if self.mode.requires_const_checking() {
13061344
let mut err = self.tcx.sess.struct_span_err(
13071345
self.span,
13081346
&format!("function pointers are not allowed in const fn"));
@@ -1361,7 +1399,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
13611399
self.super_terminator_kind(kind, location);
13621400

13631401
// Deny *any* live drops anywhere other than functions.
1364-
if self.mode != Mode::Fn {
1402+
if self.mode.requires_const_checking() {
13651403
unleash_miri!(self);
13661404
// HACK(eddyb): emulate a bit of dataflow analysis,
13671405
// conservatively, that drop elaboration will do.
@@ -1472,12 +1510,12 @@ impl MirPass for QualifyAndPromoteConstants {
14721510
let id = tcx.hir().as_local_hir_id(def_id).unwrap();
14731511
let mut const_promoted_temps = None;
14741512
let mode = match tcx.hir().body_owner_kind_by_hir_id(id) {
1475-
hir::BodyOwnerKind::Closure => Mode::Fn,
1513+
hir::BodyOwnerKind::Closure => Mode::NonConstFn,
14761514
hir::BodyOwnerKind::Fn => {
14771515
if tcx.is_const_fn(def_id) {
14781516
Mode::ConstFn
14791517
} else {
1480-
Mode::Fn
1518+
Mode::NonConstFn
14811519
}
14821520
}
14831521
hir::BodyOwnerKind::Const => {
@@ -1489,7 +1527,7 @@ impl MirPass for QualifyAndPromoteConstants {
14891527
};
14901528

14911529
debug!("run_pass: mode={:?}", mode);
1492-
if mode == Mode::Fn || mode == Mode::ConstFn {
1530+
if mode == Mode::NonConstFn || mode == Mode::ConstFn {
14931531
// This is ugly because Checker holds onto mir,
14941532
// which can't be mutated until its scope ends.
14951533
let (temps, candidates) = {

0 commit comments

Comments
 (0)