Skip to content

Commit 5ed8ac4

Browse files
Rollup merge of #78272 - lcnr:abstract-const-unused-node, r=oli-obk
const_evaluatable_checked: deal with unused nodes + div r? @oli-obk
2 parents 597b4c5 + 47cb871 commit 5ed8ac4

File tree

4 files changed

+132
-21
lines changed

4 files changed

+132
-21
lines changed

compiler/rustc_trait_selection/src/traits/const_evaluatable.rs

+64-21
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,23 @@ impl AbstractConst<'tcx> {
223223
}
224224
}
225225

226+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227+
struct WorkNode<'tcx> {
228+
node: Node<'tcx>,
229+
span: Span,
230+
used: bool,
231+
}
232+
226233
struct AbstractConstBuilder<'a, 'tcx> {
227234
tcx: TyCtxt<'tcx>,
228235
body: &'a mir::Body<'tcx>,
229236
/// The current WIP node tree.
230-
nodes: IndexVec<NodeId, Node<'tcx>>,
237+
///
238+
/// We require all nodes to be used in the final abstract const,
239+
/// so we store this here. Note that we also consider nodes as used
240+
/// if they are mentioned in an assert, so some used nodes are never
241+
/// actually reachable by walking the [`AbstractConst`].
242+
nodes: IndexVec<NodeId, WorkNode<'tcx>>,
231243
locals: IndexVec<mir::Local, NodeId>,
232244
/// We only allow field accesses if they access
233245
/// the result of a checked operation.
@@ -274,6 +286,27 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
274286
Ok(Some(builder))
275287
}
276288

289+
fn add_node(&mut self, node: Node<'tcx>, span: Span) -> NodeId {
290+
// Mark used nodes.
291+
match node {
292+
Node::Leaf(_) => (),
293+
Node::Binop(_, lhs, rhs) => {
294+
self.nodes[lhs].used = true;
295+
self.nodes[rhs].used = true;
296+
}
297+
Node::UnaryOp(_, input) => {
298+
self.nodes[input].used = true;
299+
}
300+
Node::FunctionCall(func, nodes) => {
301+
self.nodes[func].used = true;
302+
nodes.iter().for_each(|&n| self.nodes[n].used = true);
303+
}
304+
}
305+
306+
// Nodes start as unused.
307+
self.nodes.push(WorkNode { node, span, used: false })
308+
}
309+
277310
fn place_to_local(
278311
&mut self,
279312
span: Span,
@@ -311,7 +344,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
311344
let local = self.place_to_local(span, p)?;
312345
Ok(self.locals[local])
313346
}
314-
mir::Operand::Constant(ct) => Ok(self.nodes.push(Node::Leaf(ct.literal))),
347+
mir::Operand::Constant(ct) => Ok(self.add_node(Node::Leaf(ct.literal), span)),
315348
}
316349
}
317350

@@ -336,38 +369,38 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
336369

337370
fn build_statement(&mut self, stmt: &mir::Statement<'tcx>) -> Result<(), ErrorReported> {
338371
debug!("AbstractConstBuilder: stmt={:?}", stmt);
372+
let span = stmt.source_info.span;
339373
match stmt.kind {
340374
StatementKind::Assign(box (ref place, ref rvalue)) => {
341-
let local = self.place_to_local(stmt.source_info.span, place)?;
375+
let local = self.place_to_local(span, place)?;
342376
match *rvalue {
343377
Rvalue::Use(ref operand) => {
344-
self.locals[local] =
345-
self.operand_to_node(stmt.source_info.span, operand)?;
378+
self.locals[local] = self.operand_to_node(span, operand)?;
346379
Ok(())
347380
}
348381
Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
349-
let lhs = self.operand_to_node(stmt.source_info.span, lhs)?;
350-
let rhs = self.operand_to_node(stmt.source_info.span, rhs)?;
351-
self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs));
382+
let lhs = self.operand_to_node(span, lhs)?;
383+
let rhs = self.operand_to_node(span, rhs)?;
384+
self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs), span);
352385
if op.is_checkable() {
353386
bug!("unexpected unchecked checkable binary operation");
354387
} else {
355388
Ok(())
356389
}
357390
}
358391
Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
359-
let lhs = self.operand_to_node(stmt.source_info.span, lhs)?;
360-
let rhs = self.operand_to_node(stmt.source_info.span, rhs)?;
361-
self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs));
392+
let lhs = self.operand_to_node(span, lhs)?;
393+
let rhs = self.operand_to_node(span, rhs)?;
394+
self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs), span);
362395
self.checked_op_locals.insert(local);
363396
Ok(())
364397
}
365398
Rvalue::UnaryOp(op, ref operand) if Self::check_unop(op) => {
366-
let operand = self.operand_to_node(stmt.source_info.span, operand)?;
367-
self.locals[local] = self.nodes.push(Node::UnaryOp(op, operand));
399+
let operand = self.operand_to_node(span, operand)?;
400+
self.locals[local] = self.add_node(Node::UnaryOp(op, operand), span);
368401
Ok(())
369402
}
370-
_ => self.error(Some(stmt.source_info.span), "unsupported rvalue")?,
403+
_ => self.error(Some(span), "unsupported rvalue")?,
371404
}
372405
}
373406
// These are not actually relevant for us here, so we can ignore them.
@@ -415,13 +448,9 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
415448
.map(|arg| self.operand_to_node(terminator.source_info.span, arg))
416449
.collect::<Result<Vec<NodeId>, _>>()?,
417450
);
418-
self.locals[local] = self.nodes.push(Node::FunctionCall(func, args));
451+
self.locals[local] = self.add_node(Node::FunctionCall(func, args), fn_span);
419452
Ok(Some(target))
420453
}
421-
// We only allow asserts for checked operations.
422-
//
423-
// These asserts seem to all have the form `!_local.0` so
424-
// we only allow exactly that.
425454
TerminatorKind::Assert { ref cond, expected: false, target, .. } => {
426455
let p = match cond {
427456
mir::Operand::Copy(p) | mir::Operand::Move(p) => p,
@@ -430,7 +459,15 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
430459

431460
const ONE_FIELD: mir::Field = mir::Field::from_usize(1);
432461
debug!("proj: {:?}", p.projection);
433-
if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() {
462+
if let Some(p) = p.as_local() {
463+
debug_assert!(!self.checked_op_locals.contains(p));
464+
// Mark locals directly used in asserts as used.
465+
//
466+
// This is needed because division does not use `CheckedBinop` but instead
467+
// adds an explicit assert for `divisor != 0`.
468+
self.nodes[self.locals[p]].used = true;
469+
return Ok(Some(target));
470+
} else if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() {
434471
// Only allow asserts checking the result of a checked operation.
435472
if self.checked_op_locals.contains(p.local) {
436473
return Ok(Some(target));
@@ -457,7 +494,13 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
457494
if let Some(next) = self.build_terminator(block.terminator())? {
458495
block = &self.body.basic_blocks()[next];
459496
} else {
460-
return Ok(self.tcx.arena.alloc_from_iter(self.nodes));
497+
assert_eq!(self.locals[mir::RETURN_PLACE], self.nodes.last().unwrap());
498+
self.nodes[self.locals[mir::RETURN_PLACE]].used = true;
499+
if let Some(&unused) = self.nodes.iter().find(|n| !n.used) {
500+
self.error(Some(unused.span), "dead code")?;
501+
}
502+
503+
return Ok(self.tcx.arena.alloc_from_iter(self.nodes.into_iter().map(|n| n.node)));
461504
}
462505
}
463506
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// run-pass
2+
#![feature(const_generics, const_evaluatable_checked)]
3+
#![allow(incomplete_features)]
4+
5+
fn with_bound<const N: usize>() where [u8; N / 2]: Sized {
6+
let _: [u8; N / 2] = [0; N / 2];
7+
}
8+
9+
fn main() {
10+
with_bound::<4>();
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![feature(const_generics, const_evaluatable_checked)]
2+
#![allow(incomplete_features)]
3+
4+
fn add<const N: usize>() -> [u8; { N + 1; 5 }] {
5+
//~^ ERROR overly complex generic constant
6+
todo!()
7+
}
8+
9+
fn div<const N: usize>() -> [u8; { N / 1; 5 }] {
10+
//~^ ERROR overly complex generic constant
11+
todo!()
12+
}
13+
14+
const fn foo(n: usize) {}
15+
16+
fn fn_call<const N: usize>() -> [u8; { foo(N); 5 }] {
17+
//~^ ERROR overly complex generic constant
18+
todo!()
19+
}
20+
21+
fn main() {
22+
add::<12>();
23+
div::<9>();
24+
fn_call::<14>();
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: overly complex generic constant
2+
--> $DIR/unused_expr.rs:4:34
3+
|
4+
LL | fn add<const N: usize>() -> [u8; { N + 1; 5 }] {
5+
| ^^-----^^^^^
6+
| |
7+
| dead code
8+
|
9+
= help: consider moving this anonymous constant into a `const` function
10+
11+
error: overly complex generic constant
12+
--> $DIR/unused_expr.rs:9:34
13+
|
14+
LL | fn div<const N: usize>() -> [u8; { N / 1; 5 }] {
15+
| ^^-----^^^^^
16+
| |
17+
| dead code
18+
|
19+
= help: consider moving this anonymous constant into a `const` function
20+
21+
error: overly complex generic constant
22+
--> $DIR/unused_expr.rs:16:38
23+
|
24+
LL | fn fn_call<const N: usize>() -> [u8; { foo(N); 5 }] {
25+
| ^^------^^^^^
26+
| |
27+
| dead code
28+
|
29+
= help: consider moving this anonymous constant into a `const` function
30+
31+
error: aborting due to 3 previous errors
32+

0 commit comments

Comments
 (0)