1
1
use rustc_data_structures:: fx:: { FxIndexMap , FxIndexSet } ;
2
2
use rustc_index:: { Idx , IndexVec } ;
3
3
use std:: cell:: RefCell ;
4
- use std:: collections:: BTreeMap ;
4
+ use std:: collections:: { BTreeMap , BTreeSet } ;
5
5
6
6
#[ cfg( test) ]
7
7
mod tests;
@@ -47,8 +47,18 @@ pub struct DedupResult {
47
47
struct Mapping ( BTreeMap < VarIndex , VarIndex > ) ;
48
48
#[ derive( Debug , Clone , PartialEq , Eq ) ]
49
49
struct MappingInfo {
50
- dependencies : FxIndexMap < ConstraintIndex , FxIndexSet < MappingIndex > > ,
50
+ /// The constraints that a mapping will eliminate. For example, if we have the constraints
51
+ /// [1, 2] (with ConstraintIndex of 0) and [3, 4], the mapping 1:3,2:4 will eliminate constraint 0
51
52
eliminated_constraints : FxIndexSet < ConstraintIndex > ,
53
+ /// A mapping has dependencies if it only maps a subset of the variables in a constraint, and therefore
54
+ /// depends on another mapping to complete the full mapping. For example, if we have the constraints
55
+ /// [1, 2] (index 1), [11, 12] (index 2), [2, 3] (index 3), and [12, 13] (index 4), the mapping
56
+ /// that merges the 1st constraint into the 2nd (mapping vars 1 to 11, 2 to 12) will also "partially"
57
+ /// map the 3rd constraint (because the mapping maps var 2, which the 3rd constraint contains).
58
+ /// This will partially map the 3rd constraint into [12, 3], which isn't a pre-existing constraint - HOWEVER,
59
+ /// if we also apply the mapping var2->var12,var3->var13, then it maps the constraint to [12, 13] which *is*
60
+ /// a preexisting constraint. Therefore, the two constraints depend on each other
61
+ dependencies : FxIndexMap < ConstraintIndex , BTreeSet < MappingIndex > > ,
52
62
}
53
63
#[ derive( Debug , PartialEq , Eq ) ]
54
64
enum MapEvalErr {
@@ -69,16 +79,23 @@ impl DedupSolver {
69
79
70
80
mappings : FxIndexMap :: default ( ) ,
71
81
removed_constraints : RefCell :: new ( FxIndexSet :: default ( ) ) ,
72
- applied_mappings : RefCell :: new ( Mapping :: map_constraints ( & [ ] , & [ ] ) ) ,
82
+ applied_mappings : RefCell :: new ( Mapping :: from ( & [ ] , & [ ] ) ) ,
73
83
} ;
74
84
deduper. refine_cliques ( ) ;
75
85
deduper. compute_mappings ( ) ;
76
86
deduper. resolve_dependencies ( ) ;
77
87
78
88
DedupResult { removed_constraints : deduper. removed_constraints . into_inner ( ) }
79
89
}
90
+ /// The input cliques are provided just on a basis of the structure of the constraints, i.e.
91
+ /// "are they the same if we ignore variables unnameable from the caller". However, just because
92
+ /// this is the case doesn't mean that two constraints can be merged - for example, the constraint
93
+ /// involving vars [1, 3] can't be merged with a constraint involving vars [2, 2].
94
+ /// This function refines the cliques such that if we create a graph with constraints as vertices
95
+ /// and edges if they can be merged, constraint_cliques now represents the **true** cliques of the
96
+ /// graph, i.e. any two constrains in the same clique can now create a valid mapping
80
97
fn refine_cliques ( & mut self ) {
81
- // Refine categories based on shape
98
+ // Refine categories based on shape - see canonicalize_constraint_shape for more info
82
99
for clique_indx in ( 0 ..self . constraint_cliques . len ( ) ) . map ( CliqueIndex :: new) {
83
100
let mut shape_cliques: FxIndexMap < Vec < usize > , CliqueIndex > = FxIndexMap :: default ( ) ;
84
101
let mut constraint_indx = 0 ;
@@ -103,7 +120,17 @@ impl DedupSolver {
103
120
self . constraint_cliques [ new_clique] . push ( constraint) ;
104
121
}
105
122
}
106
- // Refine categories based on indices of variables
123
+ // Refine categories based on indices of variables. This is based on the observation that
124
+ // if a variable V is present in a constraint C1 at some set of indices I, then a constraint
125
+ // C2 can be merged with C1 only if one of the following cases are satisfied:
126
+ // a. V is present in constraint C2 at the **same** set of indices I, where in that case
127
+ // the variable mapping that merges these two constraints would just map V onto V
128
+ // b. V is not present in constraint C2 at all, in which case some other variable would
129
+ // be mapped onto V
130
+ // If none of these above cases are true, that means we have a situation where we map V
131
+ // to another variable U, and a variable W would be mapped onto V - in this case, we're just
132
+ // shuffling variables around without actually eliminating any, which is unproductive and
133
+ // hence an "invalid mapping"
107
134
for clique_indx in ( 0 ..self . constraint_cliques . len ( ) ) . map ( CliqueIndex :: new) {
108
135
// First element of tuple (the FxIndexMap) maps a variable to
109
136
// the index it occurs in
@@ -178,7 +205,6 @@ impl DedupSolver {
178
205
/// Deduplication can be done greedily because if two constraints can be merged, then they're
179
206
/// equivalent in every way, including in relations to other constraints
180
207
fn compute_mappings ( & mut self ) {
181
- let mut invalid_maps: FxIndexSet < Mapping > = FxIndexSet :: default ( ) ;
182
208
for clique in self . constraint_cliques . iter ( ) {
183
209
for ( n, constraint_1) in clique
184
210
. iter ( )
@@ -191,19 +217,17 @@ impl DedupSolver {
191
217
. filter ( |x| !self . removed_constraints . borrow ( ) . contains ( * x) )
192
218
{
193
219
// Maps constraint_1 to constraint_2
194
- let forward = Mapping :: map_constraints (
220
+ let forward = Mapping :: from (
195
221
& self . constraint_vars [ * constraint_1] ,
196
222
& self . constraint_vars [ * constraint_2] ,
197
223
) ;
198
- if invalid_maps. contains ( & forward) || self . mappings . contains_key ( & forward) {
199
- continue ;
200
- }
201
224
// Maps constraint_2 to constraint_1
202
- let reverse = Mapping :: map_constraints (
225
+ let reverse = Mapping :: from (
203
226
& self . constraint_vars [ * constraint_2] ,
204
227
& self . constraint_vars [ * constraint_1] ,
205
228
) ;
206
- if invalid_maps. contains ( & reverse) || self . mappings . contains_key ( & reverse) {
229
+ if self . mappings . contains_key ( & forward) || self . mappings . contains_key ( & reverse)
230
+ {
207
231
continue ;
208
232
}
209
233
@@ -219,17 +243,15 @@ impl DedupSolver {
219
243
if eval_forward == Err ( MapEvalErr :: Conflicts )
220
244
|| eval_reverse == Err ( MapEvalErr :: Conflicts )
221
245
{
222
- invalid_maps. insert ( forward) ;
223
- invalid_maps. insert ( reverse) ;
224
246
continue ;
225
247
}
226
248
if let Ok ( eval_forward) = eval_forward {
227
- if ! self . try_apply_mapping ( & forward, & eval_forward, false ) {
249
+ if self . try_apply_mapping ( & forward, & eval_forward, false ) == Err ( true ) {
228
250
self . mappings . insert ( forward, eval_forward) ;
229
251
}
230
252
}
231
253
if let Ok ( eval_reverse) = eval_reverse {
232
- if ! self . try_apply_mapping ( & reverse, & eval_reverse, false ) {
254
+ if self . try_apply_mapping ( & reverse, & eval_reverse, false ) == Err ( true ) {
233
255
self . mappings . insert ( reverse, eval_reverse) ;
234
256
}
235
257
}
@@ -258,7 +280,7 @@ impl DedupSolver {
258
280
let mut found_non_conflicting = false ;
259
281
for constraint_2 in clique. iter ( ) {
260
282
let vars_2 = & self . constraint_vars [ * constraint_2] ;
261
- let trial_mapping = Mapping :: map_constraints ( vars_1, vars_2) ;
283
+ let trial_mapping = Mapping :: from ( vars_1, vars_2) ;
262
284
if mapping. conflicts_with ( & trial_mapping) {
263
285
continue ;
264
286
}
@@ -267,7 +289,7 @@ impl DedupSolver {
267
289
if !mapping. contains_fully ( & trial_mapping) {
268
290
// The input mapping can be applied only if there's another mapping that
269
291
// maps every variable in constraint_1 (and doesn't conflict with the input mapping)
270
- info. dependencies . insert ( * constraint_1, FxIndexSet :: default ( ) ) ;
292
+ info. dependencies . insert ( * constraint_1, BTreeSet :: default ( ) ) ;
271
293
continue ;
272
294
}
273
295
if * constraint_1 != * constraint_2 {
@@ -302,46 +324,41 @@ impl DedupSolver {
302
324
true
303
325
} ) ;
304
326
// A map from a constraint to the mappings that will eliminate it (i.e. map it fully)
305
- let mut constraint_mappings: FxIndexMap < ConstraintIndex , FxIndexSet < MappingIndex > > =
327
+ let mut constraint_mappings: FxIndexMap < ConstraintIndex , BTreeSet < MappingIndex > > =
306
328
FxIndexMap :: default ( ) ;
307
329
for ( indx, ( _, mapping_info) ) in self . mappings . iter ( ) . enumerate ( ) {
308
330
for eliminated_constraint in mapping_info. eliminated_constraints . iter ( ) {
309
331
constraint_mappings
310
332
. entry ( * eliminated_constraint)
311
- . or_insert_with ( FxIndexSet :: default)
333
+ . or_insert_with ( BTreeSet :: default)
312
334
. insert ( MappingIndex :: new ( indx) ) ;
313
335
}
314
336
}
315
337
for indx in ( 0 ..self . mappings . len ( ) ) . map ( MappingIndex :: new) {
316
338
let mapping = self . get_mapping ( indx) ;
317
339
let input_dependencies = & self . get_mapping_info ( indx) . dependencies ;
318
- let mut dependencies = IndexVec :: new ( ) ;
340
+ let mut dependencies = FxIndexSet :: default ( ) ;
319
341
for ( dependency, _) in input_dependencies. iter ( ) {
320
342
// The set of mappings that can resolve this dependency
321
- let mut resolve_options = FxIndexSet :: default ( ) ;
322
- if let Some ( resolve_mappings) = constraint_mappings. get ( dependency) {
323
- resolve_options. extend (
324
- resolve_mappings
325
- . iter ( )
326
- . filter ( |x| !mapping. conflicts_with ( & self . get_mapping ( * * x) ) )
327
- . cloned ( ) ,
328
- )
329
- }
330
- // Don't duplicate dependency groups
331
- if dependencies. iter ( ) . any ( |x| x == & resolve_options) {
332
- continue ;
333
- }
334
- dependencies. push ( resolve_options) ;
343
+ let mut resolve_options =
344
+ constraint_mappings. get ( dependency) . cloned ( ) . unwrap_or_else ( BTreeSet :: new) ;
345
+ resolve_options. retain ( |x| !mapping. conflicts_with ( & self . get_mapping ( * x) ) ) ;
346
+ dependencies. insert ( resolve_options) ;
335
347
}
336
348
// After this point, the actual constraints that a dependency maps
337
349
// stops mattering - all that matters is that the dependency *exists*
338
- self . mappings . get_index_mut ( indx. index ( ) ) . unwrap ( ) . 1 . dependencies =
339
- dependencies. into_iter_enumerated ( ) . collect ( ) ;
350
+ let old_dependencies =
351
+ & mut self . mappings . get_index_mut ( indx. index ( ) ) . unwrap ( ) . 1 . dependencies ;
352
+ * old_dependencies = dependencies
353
+ . into_iter ( )
354
+ . enumerate ( )
355
+ . map ( |( indx, x) | ( ConstraintIndex :: from ( indx) , x) )
356
+ . collect ( ) ;
340
357
}
341
358
}
342
359
343
- /// Resolves dependencies on mappings - i.e. we find a set of mappings that mutually satisfy each other's
344
- /// dependencies, and don't conflict
360
+ /// Resolves dependencies on mappings - i.e. we find sets of mappings that mutually satisfy each other's
361
+ /// dependencies, and don't conflict, then apply these mappings
345
362
fn resolve_dependencies ( & mut self ) {
346
363
let mut used_mappings = FxIndexSet :: default ( ) ;
347
364
for indx in ( 0 ..self . mappings . len ( ) ) . map ( MappingIndex :: new) {
@@ -366,66 +383,81 @@ impl DedupSolver {
366
383
self . get_mapping_info ( mapping) ,
367
384
true ,
368
385
) ;
369
- assert ! ( application_result) ;
386
+ assert ! ( application_result. is_ok ( ) ) ;
370
387
used_mappings. insert ( mapping) ;
371
388
}
372
389
}
373
390
}
374
391
}
375
- /// Performs a depth-first search on the dependencies graph of mappings. Each call to this functino
376
- /// takes in 3 arguments - the mappings that are already presumed to be part of the set of mappings
377
- /// that should be applied together, these mappings aggregated into a single mapping, and a `from` set,
378
- /// i.e. a set of mappings that we just added, and thus might have unresolved dependencies
379
- /// There's quite a few heuristics that will probably yield *significant* speedups
380
- /// I'll look into that later, if the rest of this approach is sound
392
+ /// Finds a set of mappings that mutually satisfy each other's dependencies without conflicting with each
393
+ /// other, or the mappings that have already been applied. It does this through depth first search -
394
+ /// it takes a FxIndexSet of the mappings that have already been presumed to be part of the mapping set as well
395
+ /// as a FxIndexSet of mappings that we are trying to add to this set (`from`). These mappings still may have
396
+ /// dependencies that might be unresolved, so dfs_search attempts to resolve these dependencies, recursively calling
397
+ /// itself if necessary. If `from` has no unresolved dependencies, then a set of mappings is found, and we return
381
398
fn dfs_search (
382
399
& self ,
383
400
mut used_mappings : FxIndexSet < MappingIndex > ,
384
401
mut applied_mappings : Mapping ,
385
- from : FxIndexSet < MappingIndex > ,
402
+ mut from : FxIndexSet < MappingIndex > ,
386
403
) -> Option < FxIndexSet < MappingIndex > > {
404
+ // Apply the mappings that we're trying to apply (in `from`), aborting if there's any conflicts
387
405
for mapping_indx in from. iter ( ) {
388
406
let ( mapping, _) = self . mappings . get_index ( mapping_indx. index ( ) ) . unwrap ( ) ;
389
407
if applied_mappings. conflicts_with ( mapping) {
390
408
return None ;
391
409
}
392
410
applied_mappings. 0 . extend ( & mapping. 0 ) ;
393
411
}
394
- if from. iter ( ) . all ( |x| used_mappings. contains ( x) ) {
412
+ // If we already applied a mapping, we now remove it from `from`, as its dependencies have
413
+ // been resolved and therefore we don't need to worry about it
414
+ from. retain ( |x| !used_mappings. contains ( x) ) ;
415
+ if from. is_empty ( ) {
395
416
return Some ( used_mappings) ;
396
417
}
397
418
used_mappings. extend ( from. iter ( ) ) ;
398
419
399
- // For each unresolved dependency, we have a set of Mappings that can resolve it
400
- let unresolved_dependencies: Vec < & FxIndexSet < MappingIndex > > =
401
- from. iter ( ) . flat_map ( |x| self . get_mapping_info ( * x) . dependencies . values ( ) ) . collect ( ) ;
402
- if unresolved_dependencies. is_empty ( ) {
403
- return Some ( used_mappings) ;
420
+ // For each unresolved dependency, we have a list of Mappings that can resolve it
421
+ let mut unresolved_dependencies: FxIndexSet < Vec < MappingIndex > > = FxIndexSet :: default ( ) ;
422
+ for from_mapping in from. iter ( ) {
423
+ let resolve_options = self . get_mapping_info ( * from_mapping) . dependencies . values ( ) ;
424
+ let resolve_options = resolve_options. map ( |x| {
425
+ Vec :: from_iter (
426
+ x. iter ( )
427
+ . cloned ( )
428
+ // Throw out mappings that conflict with the current `used_mappings` we're
429
+ // trying to satisfy the dependencies of
430
+ . filter ( |x| !self . get_mapping ( * x) . conflicts_with ( & applied_mappings) ) ,
431
+ )
432
+ } ) ;
433
+ unresolved_dependencies. extend ( resolve_options) ;
434
+ }
435
+ if unresolved_dependencies. iter ( ) . any ( |x| x. is_empty ( ) ) {
436
+ return None ;
404
437
}
405
- // For each unresolved dependency, we have an index denoting the index in the FxIndexSet that
406
- // we will try applying next
407
- // Essentially, we just sweep through all the combinations exhaustively, e.g. if we have 3
408
- // unresolved dependencies, each with 3 options, we would go "[0, 0, 0]", "[1, 0, 0]", "[2, 0, 0]",
409
- // "[0, 1, 0]", "[1, 1, 0]", "[2, 1, 0]", so on and so forth until we find one that succeeds
438
+ // For each unresolved dependency, we have a list of Mappings that can resolve it. The idea is that
439
+ // we need to pick one mapping from each list to create the set of mappings we're going to try adding
440
+ // to the set of mappings, and right now, it's done incredibly naively - for each list of mappings,
441
+ // we store the value that denotes the index of the mapping in that list that we're going to try
442
+ // adding next. We start out with a vec of all zeroes, i.e. we try taking the first element of each
443
+ // list, and we sweep the indices in order, e.g. going [0, 0, 0], [1, 0, 0], [2, 0, 0], [3, 0, 0],
444
+ // [0, 1, 0], [1, 1, 0], [2, 1, 0], [3, 1, 0], [0, 2, 0], so on and so forth
410
445
let mut trial_indices = vec ! [ 0 ; unresolved_dependencies. len( ) ] ;
411
446
while * trial_indices. last ( ) . unwrap ( ) < unresolved_dependencies. last ( ) . unwrap ( ) . len ( ) {
412
- let choice: FxIndexSet < MappingIndex > = trial_indices
413
- . iter ( )
414
- . zip ( & unresolved_dependencies)
415
- . map ( |( x, y) | * y. get_index ( * x) . unwrap ( ) )
416
- . collect ( ) ;
447
+ // The set of mappings that were chosen to be added next
448
+ let choice: FxIndexSet < MappingIndex > =
449
+ trial_indices. iter ( ) . zip ( & unresolved_dependencies) . map ( |( x, y) | y[ * x] ) . collect ( ) ;
417
450
let search_result =
418
451
self . dfs_search ( used_mappings. clone ( ) , applied_mappings. clone ( ) , choice) ;
419
452
if search_result. is_some ( ) {
420
453
return search_result;
421
454
}
422
455
423
- for ( val, limit) in
424
- trial_indices. iter_mut ( ) . zip ( unresolved_dependencies. iter ( ) . map ( |x| x. len ( ) ) )
425
- {
426
- * val += 1 ;
427
- if * val >= limit {
428
- * val = 0 ;
456
+ // Advance the indices to the next possibility
457
+ for ( indx, options) in trial_indices. iter_mut ( ) . zip ( & unresolved_dependencies) {
458
+ * indx += 1 ;
459
+ if * indx >= options. len ( ) {
460
+ * indx = 0 ;
429
461
continue ;
430
462
}
431
463
break ;
@@ -434,21 +466,24 @@ impl DedupSolver {
434
466
None
435
467
}
436
468
469
+ /// Tries to apply a mapping, returning Ok(()) if the application was a success, Err(true) if the
470
+ /// application failed but only because of unresolved dependencies, and Err(false) if the application
471
+ /// fails because of conflicts
437
472
fn try_apply_mapping (
438
473
& self ,
439
474
mapping : & Mapping ,
440
475
info : & MappingInfo ,
441
476
allow_dependencies : bool ,
442
- ) -> bool {
477
+ ) -> Result < ( ) , bool > {
443
478
if !allow_dependencies && !info. dependencies . is_empty ( ) {
444
- return false ;
479
+ return Err ( true ) ;
445
480
}
446
481
if self . applied_mappings . borrow ( ) . conflicts_with ( mapping) {
447
- return false ;
482
+ return Err ( false ) ;
448
483
}
449
484
self . removed_constraints . borrow_mut ( ) . extend ( info. eliminated_constraints . iter ( ) ) ;
450
485
self . applied_mappings . borrow_mut ( ) . 0 . extend ( & mapping. 0 ) ;
451
- true
486
+ Ok ( ( ) )
452
487
}
453
488
fn get_mapping ( & self , index : MappingIndex ) -> & Mapping {
454
489
& self . mappings . get_index ( index. index ( ) ) . unwrap ( ) . 0
@@ -459,7 +494,7 @@ impl DedupSolver {
459
494
}
460
495
461
496
impl Mapping {
462
- fn map_constraints ( from : & [ VarIndex ] , to : & [ VarIndex ] ) -> Self {
497
+ fn from ( from : & [ VarIndex ] , to : & [ VarIndex ] ) -> Self {
463
498
Self ( from. iter ( ) . zip ( to) . map ( |( x, y) | ( * x, * y) ) . collect ( ) )
464
499
}
465
500
fn maps_var ( & self , constraint : VarIndex ) -> Option < VarIndex > {
0 commit comments