Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit d327fa1

Browse files
committed
initial working state
1 parent 2c69266 commit d327fa1

File tree

8 files changed

+313
-13
lines changed

8 files changed

+313
-13
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//! A subset of a mir body used for const evaluatability checking.
2+
use crate::mir;
3+
use crate::ty;
4+
5+
/// An index into an `AbstractConst`.
6+
pub type NodeId = usize;
7+
8+
/// A node of an `AbstractConst`.
9+
#[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable)]
10+
pub enum Node<'tcx> {
11+
Leaf(&'tcx ty::Const<'tcx>),
12+
Binop(mir::BinOp, NodeId, NodeId),
13+
UnaryOp(mir::UnOp, NodeId),
14+
FunctionCall(NodeId, &'tcx [NodeId]),
15+
}

compiler/rustc_middle/src/mir/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use std::{iter, mem, option};
4040
use self::predecessors::{PredecessorCache, Predecessors};
4141
pub use self::query::*;
4242

43+
pub mod abstract_const;
4344
pub mod coverage;
4445
pub mod interpret;
4546
pub mod mono;

compiler/rustc_middle/src/query/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,25 @@ rustc_queries! {
244244
no_hash
245245
}
246246

247+
/// Try to build an abstract representation of the given constant.
248+
query mir_abstract_const(
249+
key: DefId
250+
) -> Option<&'tcx [mir::abstract_const::Node<'tcx>]> {
251+
desc {
252+
|tcx| "building an abstract representation for {}", tcx.def_path_str(key),
253+
}
254+
}
255+
/// Try to build an abstract representation of the given constant.
256+
query mir_abstract_const_of_const_arg(
257+
key: (LocalDefId, DefId)
258+
) -> Option<&'tcx [mir::abstract_const::Node<'tcx>]> {
259+
desc {
260+
|tcx|
261+
"building an abstract representation for the const argument {}",
262+
tcx.def_path_str(key.0.to_def_id()),
263+
}
264+
}
265+
247266
query mir_drops_elaborated_and_const_checked(
248267
key: ty::WithOptConstParam<LocalDefId>
249268
) -> &'tcx Steal<mir::Body<'tcx>> {

compiler/rustc_mir/src/transform/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,11 @@ fn mir_promoted(
329329
// this point, before we steal the mir-const result.
330330
// Also this means promotion can rely on all const checks having been done.
331331
let _ = tcx.mir_const_qualif_opt_const_arg(def);
332-
332+
let _ = if let Some(param_did) = def.const_param_did {
333+
tcx.mir_abstract_const_of_const_arg((def.did, param_did))
334+
} else {
335+
tcx.mir_abstract_const(def.did.to_def_id())
336+
};
333337
let mut body = tcx.mir_const(def).steal();
334338

335339
let mut required_consts = Vec::new();

compiler/rustc_trait_selection/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
1313
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")]
1414
#![feature(bool_to_option)]
15+
#![feature(box_patterns)]
1516
#![feature(drain_filter)]
1617
#![feature(in_band_lifetimes)]
1718
#![feature(crate_visibility_modifier)]

compiler/rustc_trait_selection/src/traits/const_evaluatable.rs

Lines changed: 244 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
#![allow(warnings)]
12
use rustc_hir::def::DefKind;
3+
use rustc_index::bit_set::BitSet;
4+
use rustc_index::vec::IndexVec;
25
use rustc_infer::infer::InferCtxt;
6+
use rustc_middle::mir::abstract_const::{Node, NodeId};
37
use rustc_middle::mir::interpret::ErrorHandled;
8+
use rustc_middle::mir::visit::Visitor;
9+
use rustc_middle::mir::{self, Rvalue, StatementKind, TerminatorKind};
10+
use rustc_middle::ty::subst::Subst;
411
use rustc_middle::ty::subst::SubstsRef;
5-
use rustc_middle::ty::{self, TypeFoldable};
12+
use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
613
use rustc_session::lint;
7-
use rustc_span::def_id::DefId;
14+
use rustc_span::def_id::{DefId, LocalDefId};
815
use rustc_span::Span;
916

1017
pub fn is_const_evaluatable<'cx, 'tcx>(
@@ -16,18 +23,23 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
1623
) -> Result<(), ErrorHandled> {
1724
debug!("is_const_evaluatable({:?}, {:?})", def, substs);
1825
if infcx.tcx.features().const_evaluatable_checked {
19-
// FIXME(const_evaluatable_checked): Actually look into generic constants to
20-
// implement const equality.
21-
for pred in param_env.caller_bounds() {
22-
match pred.skip_binders() {
23-
ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => {
24-
debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs);
25-
if b_def == def && b_substs == substs {
26-
debug!("is_const_evaluatable: caller_bound ~~> ok");
27-
return Ok(());
26+
if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs) {
27+
for pred in param_env.caller_bounds() {
28+
match pred.skip_binders() {
29+
ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => {
30+
debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs);
31+
if b_def == def && b_substs == substs {
32+
debug!("is_const_evaluatable: caller_bound ~~> ok");
33+
return Ok(());
34+
} else if AbstractConst::new(infcx.tcx, b_def, b_substs)
35+
.map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct))
36+
{
37+
debug!("is_const_evaluatable: abstract_const ~~> ok");
38+
return Ok(());
39+
}
2840
}
41+
_ => {} // don't care
2942
}
30-
_ => {} // don't care
3143
}
3244
}
3345
}
@@ -76,3 +88,223 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
7688
debug!(?concrete, "is_const_evaluatable");
7789
concrete.map(drop)
7890
}
91+
92+
/// A tree representing an anonymous constant.
93+
///
94+
/// This is only able to represent a subset of `MIR`,
95+
/// and should not leak any information about desugarings.
96+
#[derive(Clone, Copy)]
97+
pub struct AbstractConst<'tcx> {
98+
pub inner: &'tcx [Node<'tcx>],
99+
pub substs: SubstsRef<'tcx>,
100+
}
101+
102+
impl AbstractConst<'tcx> {
103+
pub fn new(
104+
tcx: TyCtxt<'tcx>,
105+
def: ty::WithOptConstParam<DefId>,
106+
substs: SubstsRef<'tcx>,
107+
) -> Option<AbstractConst<'tcx>> {
108+
let inner = match (def.did.as_local(), def.const_param_did) {
109+
(Some(did), Some(param_did)) => {
110+
tcx.mir_abstract_const_of_const_arg((did, param_did))?
111+
}
112+
_ => tcx.mir_abstract_const(def.did)?,
113+
};
114+
115+
Some(AbstractConst { inner, substs })
116+
}
117+
118+
#[inline]
119+
pub fn subtree(self, node: NodeId) -> AbstractConst<'tcx> {
120+
AbstractConst { inner: &self.inner[..=node], substs: self.substs }
121+
}
122+
123+
#[inline]
124+
pub fn root(self) -> Node<'tcx> {
125+
self.inner.last().copied().unwrap()
126+
}
127+
}
128+
129+
struct AbstractConstBuilder<'a, 'tcx> {
130+
tcx: TyCtxt<'tcx>,
131+
body: &'a mir::Body<'tcx>,
132+
nodes: Vec<Node<'tcx>>,
133+
locals: IndexVec<mir::Local, NodeId>,
134+
checked_op_locals: BitSet<mir::Local>,
135+
}
136+
137+
impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
138+
fn new(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>) -> Option<AbstractConstBuilder<'a, 'tcx>> {
139+
if body.is_cfg_cyclic() {
140+
return None;
141+
}
142+
143+
Some(AbstractConstBuilder {
144+
tcx,
145+
body,
146+
nodes: vec![],
147+
locals: IndexVec::from_elem(NodeId::MAX, &body.local_decls),
148+
checked_op_locals: BitSet::new_empty(body.local_decls.len()),
149+
})
150+
}
151+
152+
fn add_node(&mut self, n: Node<'tcx>) -> NodeId {
153+
let len = self.nodes.len();
154+
self.nodes.push(n);
155+
len
156+
}
157+
158+
fn operand_to_node(&mut self, op: &mir::Operand<'tcx>) -> Option<NodeId> {
159+
debug!("operand_to_node: op={:?}", op);
160+
const ZERO_FIELD: mir::Field = mir::Field::from_usize(0);
161+
match op {
162+
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
163+
if let Some(p) = p.as_local() {
164+
debug_assert!(!self.checked_op_locals.contains(p));
165+
Some(self.locals[p])
166+
} else if let &[mir::ProjectionElem::Field(ZERO_FIELD, _)] = p.projection.as_ref() {
167+
// Only allow field accesses on the result of checked operations.
168+
if self.checked_op_locals.contains(p.local) {
169+
Some(self.locals[p.local])
170+
} else {
171+
None
172+
}
173+
} else {
174+
None
175+
}
176+
}
177+
mir::Operand::Constant(ct) => Some(self.add_node(Node::Leaf(ct.literal))),
178+
}
179+
}
180+
181+
fn check_binop(op: mir::BinOp) -> bool {
182+
use mir::BinOp::*;
183+
match op {
184+
Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Shl | Shr | Eq | Lt | Le
185+
| Ne | Ge | Gt => true,
186+
Offset => false,
187+
}
188+
}
189+
190+
fn build(mut self) -> Option<&'tcx [Node<'tcx>]> {
191+
let mut block = &self.body.basic_blocks()[mir::START_BLOCK];
192+
loop {
193+
debug!("AbstractConstBuilder: block={:?}", block);
194+
for stmt in block.statements.iter() {
195+
debug!("AbstractConstBuilder: stmt={:?}", stmt);
196+
match stmt.kind {
197+
StatementKind::Assign(box (ref place, ref rvalue)) => {
198+
let local = place.as_local()?;
199+
match *rvalue {
200+
Rvalue::Use(ref operand) => {
201+
self.locals[local] = self.operand_to_node(operand)?;
202+
}
203+
Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
204+
let lhs = self.operand_to_node(lhs)?;
205+
let rhs = self.operand_to_node(rhs)?;
206+
self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs));
207+
if op.is_checkable() {
208+
bug!("unexpected unchecked checkable binary operation");
209+
}
210+
}
211+
Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs)
212+
if Self::check_binop(op) =>
213+
{
214+
let lhs = self.operand_to_node(lhs)?;
215+
let rhs = self.operand_to_node(rhs)?;
216+
self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs));
217+
self.checked_op_locals.insert(local);
218+
}
219+
_ => return None,
220+
}
221+
}
222+
_ => return None,
223+
}
224+
}
225+
226+
debug!("AbstractConstBuilder: terminator={:?}", block.terminator());
227+
match block.terminator().kind {
228+
TerminatorKind::Goto { target } => {
229+
block = &self.body.basic_blocks()[target];
230+
}
231+
TerminatorKind::Return => {
232+
warn!(?self.nodes);
233+
return { Some(self.tcx.arena.alloc_from_iter(self.nodes)) };
234+
}
235+
TerminatorKind::Assert { ref cond, expected: false, target, .. } => {
236+
let p = match cond {
237+
mir::Operand::Copy(p) | mir::Operand::Move(p) => p,
238+
mir::Operand::Constant(_) => bug!("Unexpected assert"),
239+
};
240+
241+
const ONE_FIELD: mir::Field = mir::Field::from_usize(1);
242+
debug!("proj: {:?}", p.projection);
243+
if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() {
244+
// Only allow asserts checking the result of a checked operation.
245+
if self.checked_op_locals.contains(p.local) {
246+
block = &self.body.basic_blocks()[target];
247+
continue;
248+
}
249+
}
250+
251+
return None;
252+
}
253+
_ => return None,
254+
}
255+
}
256+
}
257+
}
258+
259+
/// Builds an abstract const, do not use this directly, but use `AbstractConst::new` instead.
260+
pub(super) fn mir_abstract_const<'tcx>(
261+
tcx: TyCtxt<'tcx>,
262+
def: ty::WithOptConstParam<LocalDefId>,
263+
) -> Option<&'tcx [Node<'tcx>]> {
264+
if !tcx.features().const_evaluatable_checked {
265+
None
266+
} else {
267+
let body = tcx.mir_const(def).borrow();
268+
AbstractConstBuilder::new(tcx, &body)?.build()
269+
}
270+
}
271+
272+
pub fn try_unify<'tcx>(tcx: TyCtxt<'tcx>, a: AbstractConst<'tcx>, b: AbstractConst<'tcx>) -> bool {
273+
match (a.root(), b.root()) {
274+
(Node::Leaf(a_ct), Node::Leaf(b_ct)) => {
275+
let a_ct = a_ct.subst(tcx, a.substs);
276+
let b_ct = b_ct.subst(tcx, b.substs);
277+
match (a_ct.val, b_ct.val) {
278+
(ty::ConstKind::Param(a_param), ty::ConstKind::Param(b_param)) => {
279+
a_param == b_param
280+
}
281+
(ty::ConstKind::Value(a_val), ty::ConstKind::Value(b_val)) => a_val == b_val,
282+
// If we have `fn a<const N: usize>() -> [u8; N + 1]` and `fn b<const M: usize>() -> [u8; 1 + M]`
283+
// we do not want to use `assert_eq!(a(), b())` to infer that `N` and `M` have to be `1`. This
284+
// means that we can't do anything with inference variables here.
285+
(ty::ConstKind::Infer(_), _) | (_, ty::ConstKind::Infer(_)) => false,
286+
// FIXME(const_evaluatable_checked): We may want to either actually try
287+
// to evaluate `a_ct` and `b_ct` if they are are fully concrete or something like
288+
// this, for now we just return false here.
289+
_ => false,
290+
}
291+
}
292+
(Node::Binop(a_op, al, ar), Node::Binop(b_op, bl, br)) if a_op == b_op => {
293+
try_unify(tcx, a.subtree(al), b.subtree(bl))
294+
&& try_unify(tcx, a.subtree(ar), b.subtree(br))
295+
}
296+
(Node::UnaryOp(a_op, av), Node::UnaryOp(b_op, bv)) if a_op == b_op => {
297+
try_unify(tcx, a.subtree(av), b.subtree(bv))
298+
}
299+
(Node::FunctionCall(a_f, a_args), Node::FunctionCall(b_f, b_args))
300+
if a_args.len() == b_args.len() =>
301+
{
302+
try_unify(tcx, a.subtree(a_f), b.subtree(b_f))
303+
&& a_args
304+
.iter()
305+
.zip(b_args)
306+
.all(|(&an, &bn)| try_unify(tcx, a.subtree(an), b.subtree(bn)))
307+
}
308+
_ => false,
309+
}
310+
}

compiler/rustc_trait_selection/src/traits/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,20 @@ pub fn provide(providers: &mut ty::query::Providers) {
552552
vtable_methods,
553553
type_implements_trait,
554554
subst_and_check_impossible_predicates,
555+
mir_abstract_const: |tcx, def_id| {
556+
let def_id = def_id.as_local()?; // We do not store failed AbstractConst's.
557+
if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) {
558+
tcx.mir_abstract_const_of_const_arg(def)
559+
} else {
560+
const_evaluatable::mir_abstract_const(tcx, ty::WithOptConstParam::unknown(def_id))
561+
}
562+
},
563+
mir_abstract_const_of_const_arg: |tcx, (did, param_did)| {
564+
const_evaluatable::mir_abstract_const(
565+
tcx,
566+
ty::WithOptConstParam { did, const_param_did: Some(param_did) },
567+
)
568+
},
555569
..*providers
556570
};
557571
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// run-pass
2+
#![feature(const_generics, const_evaluatable_checked)]
3+
#![allow(incomplete_features)]
4+
5+
struct Foo<const B: bool>;
6+
7+
fn test<const N: usize>() -> Foo<{ N > 10 }> where Foo<{ N > 10 }>: Sized {
8+
Foo
9+
}
10+
11+
fn main() {
12+
let _: Foo<true> = test::<12>();
13+
let _: Foo<false> = test::<9>();
14+
}

0 commit comments

Comments
 (0)