Skip to content

Commit 1b275d0

Browse files
committed
document const_evaluatable
1 parent 7fff155 commit 1b275d0

File tree

1 file changed

+47
-3
lines changed

1 file changed

+47
-3
lines changed

compiler/rustc_trait_selection/src/traits/const_evaluatable.rs

+47-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
//! Checking that constant values used in types can be successfully evaluated.
2+
//!
3+
//! For concrete constants, this is fairly simple as we can just try and evaluate it.
4+
//!
5+
//! When dealing with polymorphic constants, for example `std::mem::size_of::<T>() - 1`,
6+
//! this is not as easy.
7+
//!
8+
//! In this case we try to build an abstract representation of this constant using
9+
//! `mir_abstract_const` which can then be checked for structural equality with other
10+
//! generic constants mentioned in the `caller_bounds` of the current environment.
111
use rustc_hir::def::DefKind;
212
use rustc_index::bit_set::BitSet;
313
use rustc_index::vec::IndexVec;
@@ -129,13 +139,19 @@ impl AbstractConst<'tcx> {
129139
struct AbstractConstBuilder<'a, 'tcx> {
130140
tcx: TyCtxt<'tcx>,
131141
body: &'a mir::Body<'tcx>,
142+
/// The current WIP node tree.
132143
nodes: IndexVec<NodeId, Node<'tcx>>,
133144
locals: IndexVec<mir::Local, NodeId>,
145+
/// We only allow field accesses if they access
146+
/// the result of a checked operation.
134147
checked_op_locals: BitSet<mir::Local>,
135148
}
136149

137150
impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
138151
fn new(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>) -> Option<AbstractConstBuilder<'a, 'tcx>> {
152+
// We only allow consts without control flow, so
153+
// we check for cycles here which simplifies the
154+
// rest of this implementation.
139155
if body.is_cfg_cyclic() {
140156
return None;
141157
}
@@ -154,17 +170,21 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
154170
checked_op_locals: BitSet::new_empty(body.local_decls.len()),
155171
})
156172
}
157-
158173
fn operand_to_node(&mut self, op: &mir::Operand<'tcx>) -> Option<NodeId> {
159174
debug!("operand_to_node: op={:?}", op);
160175
const ZERO_FIELD: mir::Field = mir::Field::from_usize(0);
161176
match op {
162177
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
178+
// Do not allow any projections.
179+
//
180+
// One exception are field accesses on the result of checked operations,
181+
// which are required to support things like `1 + 2`.
163182
if let Some(p) = p.as_local() {
164183
debug_assert!(!self.checked_op_locals.contains(p));
165184
Some(self.locals[p])
166185
} else if let &[mir::ProjectionElem::Field(ZERO_FIELD, _)] = p.projection.as_ref() {
167-
// Only allow field accesses on the result of checked operations.
186+
// Only allow field accesses if the given local
187+
// contains the result of a checked operation.
168188
if self.checked_op_locals.contains(p.local) {
169189
Some(self.locals[p.local])
170190
} else {
@@ -238,6 +258,11 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
238258
}
239259
}
240260

261+
/// Possible return values:
262+
///
263+
/// - `None`: unsupported terminator, stop building
264+
/// - `Some(None)`: supported terminator, finish building
265+
/// - `Some(Some(block))`: support terminator, build `block` next
241266
fn build_terminator(
242267
&mut self,
243268
terminator: &mir::Terminator<'tcx>,
@@ -250,7 +275,18 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
250275
ref func,
251276
ref args,
252277
destination: Some((ref place, target)),
278+
// We do not care about `cleanup` here. Any branch which
279+
// uses `cleanup` will fail const-eval and they therefore
280+
// do not matter when checking for const evaluatability.
281+
//
282+
// Do note that even if `panic::catch_unwind` is made const,
283+
// we still do not have to care about this, as we do not look
284+
// into functions.
253285
cleanup: _,
286+
// Do not allow overloaded operators for now,
287+
// we probably do want to allow this in the future.
288+
//
289+
// This is currently fairly irrelevant as it requires `const Trait`s.
254290
from_hir_call: true,
255291
fn_span: _,
256292
} => {
@@ -264,10 +300,14 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
264300
self.locals[local] = self.nodes.push(Node::FunctionCall(func, args));
265301
Some(Some(target))
266302
}
303+
// We only allow asserts for checked operations.
304+
//
305+
// These asserts seem to all have the form `!_local.0` so
306+
// we only allow exactly that.
267307
TerminatorKind::Assert { ref cond, expected: false, target, .. } => {
268308
let p = match cond {
269309
mir::Operand::Copy(p) | mir::Operand::Move(p) => p,
270-
mir::Operand::Constant(_) => bug!("Unexpected assert"),
310+
mir::Operand::Constant(_) => bug!("unexpected assert"),
271311
};
272312

273313
const ONE_FIELD: mir::Field = mir::Field::from_usize(1);
@@ -285,8 +325,11 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
285325
}
286326
}
287327

328+
/// Builds the abstract const by walking the mir from start to finish
329+
/// and bailing out when encountering an unsupported operation.
288330
fn build(mut self) -> Option<&'tcx [Node<'tcx>]> {
289331
let mut block = &self.body.basic_blocks()[mir::START_BLOCK];
332+
// We checked for a cyclic cfg above, so this should terminate.
290333
loop {
291334
debug!("AbstractConstBuilder: block={:?}", block);
292335
for stmt in block.statements.iter() {
@@ -340,6 +383,7 @@ pub(super) fn try_unify_abstract_consts<'tcx>(
340383
false
341384
}
342385

386+
/// Tries to unify two abstract constants using structural equality.
343387
pub(super) fn try_unify<'tcx>(
344388
tcx: TyCtxt<'tcx>,
345389
a: AbstractConst<'tcx>,

0 commit comments

Comments
 (0)