1+ #![ allow( warnings) ]
12use rustc_hir:: def:: DefKind ;
3+ use rustc_index:: bit_set:: BitSet ;
4+ use rustc_index:: vec:: IndexVec ;
25use rustc_infer:: infer:: InferCtxt ;
6+ use rustc_middle:: mir:: abstract_const:: { Node , NodeId } ;
37use 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 ;
411use rustc_middle:: ty:: subst:: SubstsRef ;
5- use rustc_middle:: ty:: { self , TypeFoldable } ;
12+ use rustc_middle:: ty:: { self , TyCtxt , TypeFoldable } ;
613use rustc_session:: lint;
7- use rustc_span:: def_id:: DefId ;
14+ use rustc_span:: def_id:: { DefId , LocalDefId } ;
815use rustc_span:: Span ;
916
1017pub 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+ }
0 commit comments