const_eval_checked: deal with unused nodes + div
This commit is contained in:
parent
a9cd294cf2
commit
6ad140ca19
@ -227,7 +227,12 @@ struct AbstractConstBuilder<'a, 'tcx> {
|
|||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
body: &'a mir::Body<'tcx>,
|
body: &'a mir::Body<'tcx>,
|
||||||
/// The current WIP node tree.
|
/// The current WIP node tree.
|
||||||
nodes: IndexVec<NodeId, Node<'tcx>>,
|
///
|
||||||
|
/// We require all nodes to be used in the final abstract const,
|
||||||
|
/// so we store this here. Note that we also consider nodes as used
|
||||||
|
/// if they are mentioned in an assert, so some used nodes are never
|
||||||
|
/// actually reachable by walking the [`AbstractConst`].
|
||||||
|
nodes: IndexVec<NodeId, (Node<'tcx>, bool)>,
|
||||||
locals: IndexVec<mir::Local, NodeId>,
|
locals: IndexVec<mir::Local, NodeId>,
|
||||||
/// We only allow field accesses if they access
|
/// We only allow field accesses if they access
|
||||||
/// the result of a checked operation.
|
/// the result of a checked operation.
|
||||||
@ -274,6 +279,27 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
|
|||||||
Ok(Some(builder))
|
Ok(Some(builder))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_node(&mut self, n: Node<'tcx>) -> NodeId {
|
||||||
|
// Mark used nodes.
|
||||||
|
match n {
|
||||||
|
Node::Leaf(_) => (),
|
||||||
|
Node::Binop(_, lhs, rhs) => {
|
||||||
|
self.nodes[lhs].1 = true;
|
||||||
|
self.nodes[rhs].1 = true;
|
||||||
|
}
|
||||||
|
Node::UnaryOp(_, input) => {
|
||||||
|
self.nodes[input].1 = true;
|
||||||
|
}
|
||||||
|
Node::FunctionCall(func, nodes) => {
|
||||||
|
self.nodes[func].1 = true;
|
||||||
|
nodes.iter().for_each(|&n| self.nodes[n].1 = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nodes start as unused.
|
||||||
|
self.nodes.push((n, false))
|
||||||
|
}
|
||||||
|
|
||||||
fn place_to_local(
|
fn place_to_local(
|
||||||
&mut self,
|
&mut self,
|
||||||
span: Span,
|
span: Span,
|
||||||
@ -311,7 +337,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
|
|||||||
let local = self.place_to_local(span, p)?;
|
let local = self.place_to_local(span, p)?;
|
||||||
Ok(self.locals[local])
|
Ok(self.locals[local])
|
||||||
}
|
}
|
||||||
mir::Operand::Constant(ct) => Ok(self.nodes.push(Node::Leaf(ct.literal))),
|
mir::Operand::Constant(ct) => Ok(self.add_node(Node::Leaf(ct.literal))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +374,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
|
|||||||
Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
|
Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
|
||||||
let lhs = self.operand_to_node(stmt.source_info.span, lhs)?;
|
let lhs = self.operand_to_node(stmt.source_info.span, lhs)?;
|
||||||
let rhs = self.operand_to_node(stmt.source_info.span, rhs)?;
|
let rhs = self.operand_to_node(stmt.source_info.span, rhs)?;
|
||||||
self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs));
|
self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs));
|
||||||
if op.is_checkable() {
|
if op.is_checkable() {
|
||||||
bug!("unexpected unchecked checkable binary operation");
|
bug!("unexpected unchecked checkable binary operation");
|
||||||
} else {
|
} else {
|
||||||
@ -358,13 +384,13 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
|
|||||||
Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
|
Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
|
||||||
let lhs = self.operand_to_node(stmt.source_info.span, lhs)?;
|
let lhs = self.operand_to_node(stmt.source_info.span, lhs)?;
|
||||||
let rhs = self.operand_to_node(stmt.source_info.span, rhs)?;
|
let rhs = self.operand_to_node(stmt.source_info.span, rhs)?;
|
||||||
self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs));
|
self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs));
|
||||||
self.checked_op_locals.insert(local);
|
self.checked_op_locals.insert(local);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Rvalue::UnaryOp(op, ref operand) if Self::check_unop(op) => {
|
Rvalue::UnaryOp(op, ref operand) if Self::check_unop(op) => {
|
||||||
let operand = self.operand_to_node(stmt.source_info.span, operand)?;
|
let operand = self.operand_to_node(stmt.source_info.span, operand)?;
|
||||||
self.locals[local] = self.nodes.push(Node::UnaryOp(op, operand));
|
self.locals[local] = self.add_node(Node::UnaryOp(op, operand));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => self.error(Some(stmt.source_info.span), "unsupported rvalue")?,
|
_ => self.error(Some(stmt.source_info.span), "unsupported rvalue")?,
|
||||||
@ -415,13 +441,9 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
|
|||||||
.map(|arg| self.operand_to_node(terminator.source_info.span, arg))
|
.map(|arg| self.operand_to_node(terminator.source_info.span, arg))
|
||||||
.collect::<Result<Vec<NodeId>, _>>()?,
|
.collect::<Result<Vec<NodeId>, _>>()?,
|
||||||
);
|
);
|
||||||
self.locals[local] = self.nodes.push(Node::FunctionCall(func, args));
|
self.locals[local] = self.add_node(Node::FunctionCall(func, args));
|
||||||
Ok(Some(target))
|
Ok(Some(target))
|
||||||
}
|
}
|
||||||
// We only allow asserts for checked operations.
|
|
||||||
//
|
|
||||||
// These asserts seem to all have the form `!_local.0` so
|
|
||||||
// we only allow exactly that.
|
|
||||||
TerminatorKind::Assert { ref cond, expected: false, target, .. } => {
|
TerminatorKind::Assert { ref cond, expected: false, target, .. } => {
|
||||||
let p = match cond {
|
let p = match cond {
|
||||||
mir::Operand::Copy(p) | mir::Operand::Move(p) => p,
|
mir::Operand::Copy(p) | mir::Operand::Move(p) => p,
|
||||||
@ -430,7 +452,15 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
|
|||||||
|
|
||||||
const ONE_FIELD: mir::Field = mir::Field::from_usize(1);
|
const ONE_FIELD: mir::Field = mir::Field::from_usize(1);
|
||||||
debug!("proj: {:?}", p.projection);
|
debug!("proj: {:?}", p.projection);
|
||||||
if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() {
|
if let Some(p) = p.as_local() {
|
||||||
|
debug_assert!(!self.checked_op_locals.contains(p));
|
||||||
|
// Mark locals directly used in asserts as used.
|
||||||
|
//
|
||||||
|
// This is needed because division does not use `CheckedBinop` but instead
|
||||||
|
// adds an explicit assert for `divisor != 0`.
|
||||||
|
self.nodes[self.locals[p]].1 = true;
|
||||||
|
return Ok(Some(target));
|
||||||
|
} else if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() {
|
||||||
// Only allow asserts checking the result of a checked operation.
|
// Only allow asserts checking the result of a checked operation.
|
||||||
if self.checked_op_locals.contains(p.local) {
|
if self.checked_op_locals.contains(p.local) {
|
||||||
return Ok(Some(target));
|
return Ok(Some(target));
|
||||||
@ -457,7 +487,16 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
|
|||||||
if let Some(next) = self.build_terminator(block.terminator())? {
|
if let Some(next) = self.build_terminator(block.terminator())? {
|
||||||
block = &self.body.basic_blocks()[next];
|
block = &self.body.basic_blocks()[next];
|
||||||
} else {
|
} else {
|
||||||
return Ok(self.tcx.arena.alloc_from_iter(self.nodes));
|
assert_eq!(self.locals[mir::Local::from_usize(0)], self.nodes.last().unwrap());
|
||||||
|
self.nodes[self.locals[mir::Local::from_usize(0)]].1 = true;
|
||||||
|
if !self.nodes.iter().all(|n| n.1) {
|
||||||
|
self.error(None, "unused node")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(self
|
||||||
|
.tcx
|
||||||
|
.arena
|
||||||
|
.alloc_from_iter(self.nodes.into_iter().map(|(n, _used)| n)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
// run-pass
|
||||||
|
#![feature(const_generics, const_evaluatable_checked)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
fn with_bound<const N: usize>() where [u8; N / 2]: Sized {
|
||||||
|
let _: [u8; N / 2] = [0; N / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
with_bound::<4>();
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
#![feature(const_generics, const_evaluatable_checked)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
fn add<const N: usize>() -> [u8; { N + 1; 5 }] {
|
||||||
|
//~^ ERROR overly complex generic constant
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn div<const N: usize>() -> [u8; { N / 1; 5 }] {
|
||||||
|
//~^ ERROR overly complex generic constant
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn foo(n: usize) {}
|
||||||
|
|
||||||
|
fn fn_call<const N: usize>() -> [u8; { foo(N); 5 }] {
|
||||||
|
//~^ ERROR overly complex generic constant
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
add::<12>();
|
||||||
|
div::<9>();
|
||||||
|
fn_call::<14>();
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
error: overly complex generic constant
|
||||||
|
--> $DIR/unused_expr.rs:4:34
|
||||||
|
|
|
||||||
|
LL | fn add<const N: usize>() -> [u8; { N + 1; 5 }] {
|
||||||
|
| ^^^^^^^^^^^^ unused node
|
||||||
|
|
|
||||||
|
= help: consider moving this anonymous constant into a `const` function
|
||||||
|
|
||||||
|
error: overly complex generic constant
|
||||||
|
--> $DIR/unused_expr.rs:9:34
|
||||||
|
|
|
||||||
|
LL | fn div<const N: usize>() -> [u8; { N / 1; 5 }] {
|
||||||
|
| ^^^^^^^^^^^^ unused node
|
||||||
|
|
|
||||||
|
= help: consider moving this anonymous constant into a `const` function
|
||||||
|
|
||||||
|
error: overly complex generic constant
|
||||||
|
--> $DIR/unused_expr.rs:16:38
|
||||||
|
|
|
||||||
|
LL | fn fn_call<const N: usize>() -> [u8; { foo(N); 5 }] {
|
||||||
|
| ^^^^^^^^^^^^^ unused node
|
||||||
|
|
|
||||||
|
= help: consider moving this anonymous constant into a `const` function
|
||||||
|
|
||||||
|
error: aborting due to 3 previous errors
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user