1
+ #![ allow( warnings) ]
1
2
use rustc_hir:: def:: DefKind ;
3
+ use rustc_index:: bit_set:: BitSet ;
4
+ use rustc_index:: vec:: IndexVec ;
2
5
use rustc_infer:: infer:: InferCtxt ;
6
+ use rustc_middle:: mir:: abstract_const:: { Node , NodeId } ;
3
7
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 ;
4
11
use rustc_middle:: ty:: subst:: SubstsRef ;
5
- use rustc_middle:: ty:: { self , TypeFoldable } ;
12
+ use rustc_middle:: ty:: { self , TyCtxt , TypeFoldable } ;
6
13
use rustc_session:: lint;
7
- use rustc_span:: def_id:: DefId ;
14
+ use rustc_span:: def_id:: { DefId , LocalDefId } ;
8
15
use rustc_span:: Span ;
9
16
10
17
pub fn is_const_evaluatable < ' cx , ' tcx > (
@@ -16,18 +23,23 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
16
23
) -> Result < ( ) , ErrorHandled > {
17
24
debug ! ( "is_const_evaluatable({:?}, {:?})" , def, substs) ;
18
25
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
+ }
28
40
}
41
+ _ => { } // don't care
29
42
}
30
- _ => { } // don't care
31
43
}
32
44
}
33
45
}
@@ -76,3 +88,223 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
76
88
debug ! ( ?concrete, "is_const_evaluatable" ) ;
77
89
concrete. map ( drop)
78
90
}
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